@oneuptime/common 14.0.9 → 14.0.11
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/AIConversation.ts +127 -1
- package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
- package/Models/DatabaseModels/CephCluster.ts +225 -0
- package/Models/DatabaseModels/CloudResource.ts +12 -0
- package/Models/DatabaseModels/DatabaseServer.ts +225 -0
- package/Models/DatabaseModels/DockerHost.ts +225 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +225 -0
- package/Models/DatabaseModels/GlobalConfig.ts +1 -1
- package/Models/DatabaseModels/GlobalOidc.ts +0 -4
- package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
- package/Models/DatabaseModels/GlobalSso.ts +0 -4
- package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
- package/Models/DatabaseModels/Host.ts +225 -0
- package/Models/DatabaseModels/IncidentForm.ts +594 -0
- package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
- package/Models/DatabaseModels/Index.ts +16 -0
- package/Models/DatabaseModels/IoTFleet.ts +12 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
- package/Models/DatabaseModels/MessageQueue.ts +1102 -0
- package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
- package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
- package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
- package/Models/DatabaseModels/PodmanHost.ts +225 -0
- package/Models/DatabaseModels/Project.ts +59 -0
- package/Models/DatabaseModels/ProjectOidc.ts +0 -4
- package/Models/DatabaseModels/ProjectSso.ts +0 -4
- package/Models/DatabaseModels/ProxmoxCluster.ts +225 -0
- package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
- package/Models/DatabaseModels/RumApplication.ts +12 -0
- package/Models/DatabaseModels/RunnerJob.ts +144 -0
- package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
- package/Models/DatabaseModels/Service.ts +12 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
- package/Models/DatabaseModels/StatusPageSso.ts +0 -4
- package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
- package/Models/DatabaseModels/VMwareVCenter.ts +225 -0
- package/Server/API/AIChatAPI.ts +13 -114
- package/Server/API/AIInvestigationAPI.ts +3 -3
- package/Server/API/AIInvestigationConversationAPI.ts +266 -0
- package/Server/API/AutoRemediationAPI.ts +392 -1
- package/Server/API/IncidentFormAPI.ts +376 -0
- package/Server/API/ProjectOIDC.ts +16 -25
- package/Server/API/ProjectSSO.ts +16 -24
- package/Server/API/ResourceAiAccessAPI.ts +1669 -0
- package/Server/API/StatusPageAPI.ts +42 -77
- package/Server/Enterprise/EnterpriseEdition.ts +93 -28
- package/Server/Enterprise/EnterpriseFeature.ts +25 -3
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
- package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
- package/Server/EnvironmentConfig.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Infrastructure/Semaphore.ts +22 -0
- package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
- package/Server/Middleware/TelemetryIngest.ts +15 -5
- package/Server/Middleware/UserAuthorization.ts +16 -38
- package/Server/Services/AIService.ts +10 -13
- package/Server/Services/AnalyticsDatabaseService.ts +45 -1
- package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
- package/Server/Services/CephClusterService.ts +109 -1
- package/Server/Services/DatabaseServerService.ts +116 -1
- package/Server/Services/DockerHostService.ts +109 -1
- package/Server/Services/DockerSwarmClusterService.ts +113 -1
- package/Server/Services/HostService.ts +126 -4
- package/Server/Services/IncidentCustomFieldService.ts +112 -2
- package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
- package/Server/Services/IncidentEpisodeService.ts +10 -1
- package/Server/Services/IncidentFormService.ts +1566 -0
- package/Server/Services/IncidentFormSubmissionService.ts +97 -0
- package/Server/Services/IncidentGroupingEngineService.ts +45 -8
- package/Server/Services/IncidentService.ts +123 -5
- package/Server/Services/IncidentTemplateService.ts +22 -0
- package/Server/Services/Index.ts +14 -0
- package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
- package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
- package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
- package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
- package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
- package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
- package/Server/Services/MessageQueueService.ts +1463 -0
- package/Server/Services/MetricRecordingRuleService.ts +47 -0
- package/Server/Services/OpenTelemetryIngestService.ts +37 -4
- package/Server/Services/PodmanHostService.ts +109 -1
- package/Server/Services/ProjectService.ts +0 -59
- package/Server/Services/ProxmoxClusterService.ts +110 -1
- package/Server/Services/ResourceAiAccessService.ts +1439 -0
- package/Server/Services/ResourceAiAgentJobService.ts +202 -0
- package/Server/Services/ResourceAiAgentService.ts +2432 -0
- package/Server/Services/RunnerJobService.ts +611 -4
- package/Server/Services/StatusPagePrivateUserService.ts +1 -4
- package/Server/Services/StatusPageService.ts +1 -51
- package/Server/Services/TeamComplianceSettingService.ts +163 -42
- package/Server/Services/TelemetryUsageBillingService.ts +28 -0
- package/Server/Services/TraceRecordingRuleService.ts +33 -0
- package/Server/Services/UserNotificationRuleService.ts +8 -1
- package/Server/Services/VMwareVCenterService.ts +110 -1
- package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
- package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
- package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
- package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
- package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
- package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
- package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1442 -34
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +968 -34
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
- package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +622 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
- package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +882 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +125 -12
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +99 -15
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +99 -15
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
- package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
- package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
- package/Server/Utils/EditionEnforcement.ts +19 -202
- package/Server/Utils/LLM/LLMService.ts +259 -63
- package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/SameOriginRequest.ts +203 -0
- package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
- package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
- package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
- package/Server/Utils/VendorAssets.ts +3 -3
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +301 -225
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +38 -16
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +376 -144
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
- package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
- package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
- package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
- package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
- package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
- package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
- package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
- package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
- package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
- package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +267 -12
- package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
- package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
- package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +23 -6
- package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
- package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
- package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
- package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
- package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
- package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
- package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
- package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
- package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
- package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
- package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
- package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
- package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
- package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
- package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
- package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
- package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
- package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
- package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
- package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
- package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
- package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
- package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
- package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
- package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
- package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
- package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
- package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
- package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
- package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
- package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
- package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
- package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
- package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
- package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
- package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
- package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
- package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
- package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
- package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
- package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
- package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
- package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
- package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
- package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
- package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
- package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
- package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
- package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
- package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
- package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
- package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
- package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
- package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
- package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
- package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +2010 -0
- package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1455 -0
- package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
- package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
- package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
- package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
- package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
- package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
- package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
- package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
- package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
- package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
- package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
- package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
- package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
- package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
- package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
- package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
- package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +61 -10
- package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
- package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
- package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
- package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
- package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
- package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
- package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
- package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
- package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
- package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
- package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
- package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
- package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
- package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
- package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
- package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
- package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
- package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
- package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
- package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
- package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
- package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
- package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
- package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
- package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
- package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
- package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
- package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
- package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
- package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
- package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
- package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
- package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
- package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
- package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
- package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
- package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
- package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
- package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
- package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
- package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
- package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
- package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
- package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
- package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
- package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
- package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
- package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
- package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
- package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
- package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
- package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
- package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
- package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
- package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
- package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
- package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
- package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
- package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
- package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
- package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
- package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
- package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
- package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +694 -0
- package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
- package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
- package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
- package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
- package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
- package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
- package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1251 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
- package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
- package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +1004 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +834 -0
- package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
- package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
- package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
- package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
- package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
- package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
- package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
- package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
- package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
- package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
- package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
- package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
- package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
- package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
- package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
- package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
- package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
- package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
- package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
- package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
- package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
- package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
- package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
- package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
- package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
- package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
- package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
- package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
- package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
- package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +519 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
- package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
- package/Tests/Types/XML.test.ts +51 -0
- package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
- package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
- package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
- package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
- package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
- package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
- package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
- package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
- package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
- package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
- package/Tests/UI/Components/Icon.test.tsx +32 -0
- package/Tests/UI/Components/Input.test.tsx +31 -0
- package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
- package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
- package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
- package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
- package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
- package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
- package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
- package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
- package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
- package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
- package/Tests/UI/Components/TextArea.test.tsx +19 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
- package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
- package/Tests/UI/EsbuildConfig.test.ts +115 -39
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
- package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
- package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
- package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
- package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
- package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
- package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
- package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
- package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
- package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
- package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
- package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
- package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
- package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
- package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
- package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
- package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
- package/Types/AI/AIAgentRunLimits.ts +146 -0
- package/Types/AI/ResourceAiAccessApi.ts +140 -0
- package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
- package/Types/Code/CodeType.ts +2 -0
- package/Types/Code/JsonSyntax.ts +923 -0
- package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
- package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
- package/Types/Icon/IconProp.ts +2 -0
- package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
- package/Types/Incident/IncidentFormPublic.ts +1145 -0
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
- package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
- package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
- package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
- package/Types/MessageQueue/MessagingSystem.ts +518 -0
- package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
- package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
- package/Types/Monitor/RumAlertTemplates.ts +351 -4
- package/Types/Permission.ts +310 -0
- package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
- package/Types/ResourceAiAgent/ResourceAiAccess.ts +580 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
- package/Types/Runbook/RunbookStepType.ts +29 -0
- package/Types/Team/ComplianceNotificationChannel.ts +4 -3
- package/Types/Team/ComplianceRule.ts +71 -18
- package/Types/Team/ComplianceRuleType.ts +2 -2
- package/Types/Team/TeamComplianceStatus.ts +6 -2
- package/Types/Telemetry/EntityType.ts +16 -0
- package/Types/Telemetry/LockedFilterDetail.ts +22 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
- package/Types/XML.ts +26 -0
- package/UI/Components/CodeEditor/CodeEditor.css +379 -0
- package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
- package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
- package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
- package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
- package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
- package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
- package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
- package/UI/Components/Forms/Fields/FormField.tsx +8 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Icon/Icon.tsx +18 -0
- package/UI/Components/Input/Input.tsx +11 -4
- package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
- package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
- package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
- package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
- package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
- package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
- package/UI/Components/Table/EmptyTableMessage.ts +81 -0
- package/UI/Components/Table/Table.tsx +50 -21
- package/UI/Components/Table/TableRow.tsx +9 -1
- package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
- package/UI/Components/TextArea/TextArea.tsx +9 -2
- package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
- package/UI/Styles/Theme.css +8 -0
- package/UI/esbuild-config.js +2 -32
- package/Utils/AI/InvestigationReport.ts +121 -15
- package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
- package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
- package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
- package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
- package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
- package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
- package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
- package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
- package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
- package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
- package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
- package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
- package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
- package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
- package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
- package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
- package/Utils/Telemetry/EntityKey.ts +48 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
- package/build/dist/Models/DatabaseModels/AIConversation.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/CephCluster.js +231 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +231 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +231 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +231 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +231 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
- package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
- package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +16 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
- package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
- package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js +231 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +61 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
- package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +231 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
- package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +12 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
- package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js +231 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +9 -103
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
- package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
- package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentFormAPI.js +245 -0
- package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
- package/build/dist/Server/API/ProjectOIDC.js +16 -24
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +16 -24
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
- package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +42 -75
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.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/Infrastructure/Semaphore.js +6 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
- package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +10 -13
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +104 -0
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerService.js +99 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +104 -0
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +114 -2
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFormService.js +1202 -0
- package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
- package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
- package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +106 -4
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +14 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
- package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
- package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueService.js +1140 -0
- package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
- package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
- package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +104 -0
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +0 -54
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
- package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
- package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
- package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
- package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/RunnerJobService.js +344 -5
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +0 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
- package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
- package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
- package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
- package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
- package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
- package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1036 -32
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +661 -41
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +354 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +602 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +90 -14
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +73 -10
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +73 -10
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
- package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
- package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
- package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +3 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +186 -210
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +22 -16
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +273 -111
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
- package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
- package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
- package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
- package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
- package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
- package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/Code/CodeType.js +2 -0
- package/build/dist/Types/Code/CodeType.js.map +1 -1
- package/build/dist/Types/Code/JsonSyntax.js +604 -0
- package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
- package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
- package/build/dist/Types/Icon/IconProp.js +2 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
- package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
- package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
- package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
- package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
- package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
- package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
- package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
- package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
- package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
- package/build/dist/Types/Permission.js +274 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
- package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +281 -0
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
- package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
- package/build/dist/Types/Team/ComplianceRule.js +48 -14
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
- package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
- package/build/dist/Types/Telemetry/EntityType.js +16 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/Types/XML.js +23 -0
- package/build/dist/Types/XML.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +8 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +11 -3
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
- package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
- package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +30 -11
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +9 -2
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
- package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
- package/build/dist/Utils/AI/InvestigationReport.js +71 -11
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
- package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
- package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
- package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
- package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
- package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
- package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
- package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
- package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
- package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/package.json +1 -3
- package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
- package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
- package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
- package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
- package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
- package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
- package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
- package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
- package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
- package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
|
@@ -0,0 +1,2884 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The "create incident" and "create maintenance" forms of the Microsoft Teams
|
|
3
|
+
* bot (issue #4111).
|
|
4
|
+
*
|
|
5
|
+
* Both forms used to offer every monitor, label and on-call policy of the
|
|
6
|
+
* project (up to 10,000 of each) as dropdown choices. Teams measures a bot
|
|
7
|
+
* message as UTF-16 and refuses one over about 100 KB, often less, with HTTP
|
|
8
|
+
* 413 MessageSizeTooBig, so a customer with a few hundred monitors got "Sorry,
|
|
9
|
+
* I encountered an error processing your request" instead of a form. The
|
|
10
|
+
* builders now shorten the monitor, label and on-call policy lists until the
|
|
11
|
+
* card fits a size budget. After a 413 the card is built again for the next
|
|
12
|
+
* budget in MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES, down to 0, which
|
|
13
|
+
* leaves those lists off in one go (the card is built once). The card names
|
|
14
|
+
* whatever it left off, and links to the dashboard to create the incident or
|
|
15
|
+
* event there instead.
|
|
16
|
+
*
|
|
17
|
+
* Pinned here, for both forms: the lists are read through the
|
|
18
|
+
* MicrosoftTeamsCardChoices fetchers for the given project, sorted as the
|
|
19
|
+
* card's notes say; a small project gets every choice and nothing more; the
|
|
20
|
+
* input ids and submit action the submit handlers read are unchanged (a form
|
|
21
|
+
* already posted in a chat must still submit after an upgrade); a huge
|
|
22
|
+
* project (10,000 monitors with 100-character names, 500 labels, 200 on-call
|
|
23
|
+
* policies) gets a card within every non-zero budget, whose notes count
|
|
24
|
+
* exactly what it shows and sit right under the list they are about; the
|
|
25
|
+
* severities and monitor statuses are never shortened; the link to the
|
|
26
|
+
* dashboard is offered only when something was left off and there is a URL
|
|
27
|
+
* to offer; budget 0 keeps the required inputs and builds the card once;
|
|
28
|
+
* every card, whatever the project and budget, is a well-formed form
|
|
29
|
+
* (labelled inputs with ids of their own, compact dropdowns that are never
|
|
30
|
+
* empty, notes that wrap, one submit); the notes and the dashboard button are
|
|
31
|
+
* the elements MicrosoftTeamsCardChoices builds for both forms; the title
|
|
32
|
+
* typed after the command is prefilled within the title column's length; the
|
|
33
|
+
* maintenance form names the time zone of whoever asked for it ("Start and
|
|
34
|
+
* end times are in <zone>.", not "your time zone": in a channel, whoever fills
|
|
35
|
+
* the form in may be somewhere else) and carries it to the submit, which reads
|
|
36
|
+
* the times in that zone whatever zone the submitter is in; the legacy
|
|
37
|
+
* builders still fit the first budget.
|
|
38
|
+
*
|
|
39
|
+
* Text cut to a length must not be cut through an emoji: half of one (a lone
|
|
40
|
+
* UTF-16 surrogate) shows as a broken character. The prefilled title and long
|
|
41
|
+
* list entries are cut with truncateToLength, which keeps whole characters;
|
|
42
|
+
* the tests that say so pin what was a defect.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
46
|
+
import type { SpyInstance } from "jest-mock";
|
|
47
|
+
import IncidentSeverityService from "../../../../../Server/Services/IncidentSeverityService";
|
|
48
|
+
import LabelService from "../../../../../Server/Services/LabelService";
|
|
49
|
+
import MonitorService from "../../../../../Server/Services/MonitorService";
|
|
50
|
+
import MonitorStatusService from "../../../../../Server/Services/MonitorStatusService";
|
|
51
|
+
import OnCallDutyPolicyService from "../../../../../Server/Services/OnCallDutyPolicyService";
|
|
52
|
+
import {
|
|
53
|
+
MicrosoftTeamsIncidentActionType,
|
|
54
|
+
MicrosoftTeamsScheduledMaintenanceActionType,
|
|
55
|
+
} from "../../../../../Server/Utils/Workspace/MicrosoftTeams/Actions/ActionTypes";
|
|
56
|
+
import MicrosoftTeamsIncidentActions, {
|
|
57
|
+
MicrosoftTeamsNewIncidentFormChoices,
|
|
58
|
+
} from "../../../../../Server/Utils/Workspace/MicrosoftTeams/Actions/Incident";
|
|
59
|
+
import MicrosoftTeamsScheduledMaintenanceActions, {
|
|
60
|
+
MicrosoftTeamsNewScheduledMaintenanceFormChoices,
|
|
61
|
+
} from "../../../../../Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance";
|
|
62
|
+
import MicrosoftTeamsCardChoices, {
|
|
63
|
+
MICROSOFT_TEAMS_MAX_CHOICE_TITLE_LENGTH,
|
|
64
|
+
MICROSOFT_TEAMS_MAX_LABEL_CHOICES,
|
|
65
|
+
MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
66
|
+
MICROSOFT_TEAMS_MAX_MONITOR_STATUS_CHOICES,
|
|
67
|
+
MICROSOFT_TEAMS_MAX_ON_CALL_POLICY_CHOICES,
|
|
68
|
+
MICROSOFT_TEAMS_MAX_SEVERITY_CHOICES,
|
|
69
|
+
MicrosoftTeamsCardChoice,
|
|
70
|
+
MicrosoftTeamsCardChoiceList,
|
|
71
|
+
} from "../../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices";
|
|
72
|
+
import MicrosoftTeamsMessageSize, {
|
|
73
|
+
MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES,
|
|
74
|
+
} from "../../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize";
|
|
75
|
+
import MicrosoftTeamsTimezone, {
|
|
76
|
+
MicrosoftTeamsUserTimezone,
|
|
77
|
+
} from "../../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone";
|
|
78
|
+
import SortOrder from "../../../../../Types/BaseDatabase/SortOrder";
|
|
79
|
+
import { JSONObject } from "../../../../../Types/JSON";
|
|
80
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
81
|
+
import PositiveNumber from "../../../../../Types/PositiveNumber";
|
|
82
|
+
|
|
83
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
84
|
+
"5d7c9a2e-1f3b-4c6d-8e9f-0a1b2c3d4e5f",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const CREATE_INCIDENT_URL: string = `https://oneuptime.example.com/dashboard/${PROJECT_ID.toString()}/incidents/create`;
|
|
88
|
+
const CREATE_MAINTENANCE_URL: string = `https://oneuptime.example.com/dashboard/${PROJECT_ID.toString()}/scheduled-maintenance-events/create`;
|
|
89
|
+
|
|
90
|
+
const INCIDENT_ADD_LATER_HINT: string =
|
|
91
|
+
"You can add them to the incident in OneUptime after it is created.";
|
|
92
|
+
const MAINTENANCE_ADD_LATER_HINT: string =
|
|
93
|
+
"You can add them to the event in OneUptime after it is created.";
|
|
94
|
+
const GENERIC_TIMEZONE_NOTE: string =
|
|
95
|
+
"Start and end times are in the time zone Microsoft Teams reports for you, or in UTC if it does not report one.";
|
|
96
|
+
|
|
97
|
+
// Incident.title is LongText, ScheduledMaintenance.title is ShortText.
|
|
98
|
+
const INCIDENT_TITLE_MAX_LENGTH: number = 500;
|
|
99
|
+
const MAINTENANCE_TITLE_MAX_LENGTH: number = 100;
|
|
100
|
+
|
|
101
|
+
const HUGE_MONITOR_COUNT: number = 10000;
|
|
102
|
+
const HUGE_LABEL_COUNT: number = 500;
|
|
103
|
+
const HUGE_POLICY_COUNT: number = 200;
|
|
104
|
+
|
|
105
|
+
// The budgets a card is fitted to; 0 (no optional lists) has its own tests.
|
|
106
|
+
const NON_ZERO_BUDGETS: Array<number> =
|
|
107
|
+
MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES.filter((budget: number) => {
|
|
108
|
+
return budget > 0;
|
|
109
|
+
});
|
|
110
|
+
const FIRST_BUDGET: number = MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES[0]!;
|
|
111
|
+
|
|
112
|
+
type NamedRow = {
|
|
113
|
+
_id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
function padded(index: number): string {
|
|
118
|
+
return String(index).padStart(5, "0");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A distinct, well-formed id per table and row.
|
|
122
|
+
function rowId(table: number, index: number): string {
|
|
123
|
+
return `00000000-0000-4000-8${String(table).padStart(3, "0")}-${String(
|
|
124
|
+
index,
|
|
125
|
+
).padStart(12, "0")}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Rows in name order, as the fetchers read them.
|
|
129
|
+
function buildRows(data: {
|
|
130
|
+
table: number;
|
|
131
|
+
count: number;
|
|
132
|
+
nameOf: (index: number) => string;
|
|
133
|
+
}): Array<NamedRow> {
|
|
134
|
+
const rows: Array<NamedRow> = [];
|
|
135
|
+
|
|
136
|
+
for (let index: number = 1; index <= data.count; index++) {
|
|
137
|
+
rows.push({ _id: rowId(data.table, index), name: data.nameOf(index) });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return rows;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Exactly 100 UTF-16 units, with an emoji, which Teams counts as two.
|
|
144
|
+
function monitorName(index: number): string {
|
|
145
|
+
return `Monitor ${padded(index)} 🌍 checkout-api.eu-west-1.production.example.com / HTTPS health check ${"x".repeat(100)}`.substring(
|
|
146
|
+
0,
|
|
147
|
+
100,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* Longer than a dropdown row, with an emoji right where toChoices cuts such a
|
|
153
|
+
* name: the emoji's first half is the last code unit the cut keeps.
|
|
154
|
+
*/
|
|
155
|
+
function emojiAtTheCutName(index: number): string {
|
|
156
|
+
return `${"x".repeat(MICROSOFT_TEAMS_MAX_CHOICE_TITLE_LENGTH - 2)}🌍 edge ${padded(index)}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const SEVERITY_ROWS: Array<NamedRow> = buildRows({
|
|
160
|
+
table: 1,
|
|
161
|
+
count: 5,
|
|
162
|
+
nameOf: (index: number): string => {
|
|
163
|
+
return ["Critical", "Major", "Minor", "Warning", "Informational"][
|
|
164
|
+
index - 1
|
|
165
|
+
]!;
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const MONITOR_STATUS_ROWS: Array<NamedRow> = buildRows({
|
|
170
|
+
table: 2,
|
|
171
|
+
count: 5,
|
|
172
|
+
nameOf: (index: number): string => {
|
|
173
|
+
return [
|
|
174
|
+
"Operational",
|
|
175
|
+
"Degraded",
|
|
176
|
+
"Partial Outage",
|
|
177
|
+
"Major Outage",
|
|
178
|
+
"Under Maintenance",
|
|
179
|
+
][index - 1]!;
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const HUGE_MONITOR_ROWS: Array<NamedRow> = buildRows({
|
|
184
|
+
table: 3,
|
|
185
|
+
count: HUGE_MONITOR_COUNT,
|
|
186
|
+
nameOf: monitorName,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const HUGE_LABEL_ROWS: Array<NamedRow> = buildRows({
|
|
190
|
+
table: 4,
|
|
191
|
+
count: HUGE_LABEL_COUNT,
|
|
192
|
+
nameOf: (index: number): string => {
|
|
193
|
+
return `label ${padded(index)}: team-payments/service-tier-1`;
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const HUGE_POLICY_ROWS: Array<NamedRow> = buildRows({
|
|
198
|
+
table: 5,
|
|
199
|
+
count: HUGE_POLICY_COUNT,
|
|
200
|
+
nameOf: (index: number): string => {
|
|
201
|
+
return `On-call policy ${padded(index)} - Payments primary (EU business hours)`;
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const SMALL_MONITOR_ROWS: Array<NamedRow> = buildRows({
|
|
206
|
+
table: 6,
|
|
207
|
+
count: 3,
|
|
208
|
+
nameOf: (index: number): string => {
|
|
209
|
+
return ["API", "Checkout", "Website"][index - 1]!;
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const SMALL_LABEL_ROWS: Array<NamedRow> = buildRows({
|
|
214
|
+
table: 7,
|
|
215
|
+
count: 2,
|
|
216
|
+
nameOf: (index: number): string => {
|
|
217
|
+
return ["backend", "customer-facing"][index - 1]!;
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const SMALL_POLICY_ROWS: Array<NamedRow> = buildRows({
|
|
222
|
+
table: 8,
|
|
223
|
+
count: 2,
|
|
224
|
+
nameOf: (index: number): string => {
|
|
225
|
+
return ["Primary on-call", "Secondary on-call"][index - 1]!;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Every row as a choice, as the form read them before #4111 (no cap).
|
|
230
|
+
function listOf(rows: Array<NamedRow>): MicrosoftTeamsCardChoiceList {
|
|
231
|
+
return {
|
|
232
|
+
choices: rows.map((row: NamedRow): MicrosoftTeamsCardChoice => {
|
|
233
|
+
return { title: row.name, value: row._id };
|
|
234
|
+
}),
|
|
235
|
+
totalCount: rows.length,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function emptyList(): MicrosoftTeamsCardChoiceList {
|
|
240
|
+
return { choices: [], totalCount: 0 };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function smallIncidentChoices(): MicrosoftTeamsNewIncidentFormChoices {
|
|
244
|
+
return {
|
|
245
|
+
severities: listOf(SEVERITY_ROWS),
|
|
246
|
+
monitors: listOf(SMALL_MONITOR_ROWS),
|
|
247
|
+
monitorStatuses: listOf(MONITOR_STATUS_ROWS),
|
|
248
|
+
labels: listOf(SMALL_LABEL_ROWS),
|
|
249
|
+
onCallDutyPolicies: listOf(SMALL_POLICY_ROWS),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function smallMaintenanceChoices(): MicrosoftTeamsNewScheduledMaintenanceFormChoices {
|
|
254
|
+
return {
|
|
255
|
+
monitors: listOf(SMALL_MONITOR_ROWS),
|
|
256
|
+
monitorStatuses: listOf(MONITOR_STATUS_ROWS),
|
|
257
|
+
labels: listOf(SMALL_LABEL_ROWS),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function wholeHugeIncidentChoices(): MicrosoftTeamsNewIncidentFormChoices {
|
|
262
|
+
return {
|
|
263
|
+
severities: listOf(SEVERITY_ROWS),
|
|
264
|
+
monitors: listOf(HUGE_MONITOR_ROWS),
|
|
265
|
+
monitorStatuses: listOf(MONITOR_STATUS_ROWS),
|
|
266
|
+
labels: listOf(HUGE_LABEL_ROWS),
|
|
267
|
+
onCallDutyPolicies: listOf(HUGE_POLICY_ROWS),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function wholeHugeMaintenanceChoices(): MicrosoftTeamsNewScheduledMaintenanceFormChoices {
|
|
272
|
+
return {
|
|
273
|
+
monitors: listOf(HUGE_MONITOR_ROWS),
|
|
274
|
+
monitorStatuses: listOf(MONITOR_STATUS_ROWS),
|
|
275
|
+
labels: listOf(HUGE_LABEL_ROWS),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
type FindByArgs = {
|
|
280
|
+
query: { projectId?: unknown };
|
|
281
|
+
select?: unknown;
|
|
282
|
+
sort?: unknown;
|
|
283
|
+
limit: number | PositiveNumber;
|
|
284
|
+
skip: number | PositiveNumber;
|
|
285
|
+
props?: unknown;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
type CountByArgs = {
|
|
289
|
+
query: { projectId?: unknown };
|
|
290
|
+
props?: unknown;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// The two reads the MicrosoftTeamsCardChoices fetchers make of a table.
|
|
294
|
+
type RowTable = {
|
|
295
|
+
findBy: (findBy: FindByArgs) => Promise<Array<NamedRow>>;
|
|
296
|
+
countBy: (countBy: CountByArgs) => Promise<PositiveNumber>;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// What each table was asked for, read by read.
|
|
300
|
+
type ProjectReads = {
|
|
301
|
+
severities: Array<FindByArgs>;
|
|
302
|
+
monitorStatuses: Array<FindByArgs>;
|
|
303
|
+
monitors: Array<FindByArgs>;
|
|
304
|
+
labels: Array<FindByArgs>;
|
|
305
|
+
onCallDutyPolicies: Array<FindByArgs>;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
function toNumber(value: number | PositiveNumber): number {
|
|
309
|
+
return typeof value === "number" ? value : value.toNumber();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isThisProject(query: { projectId?: unknown }): boolean {
|
|
313
|
+
return String(query.projectId) === PROJECT_ID.toString();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* Stands in for one table of the project: findBy honours skip and limit and
|
|
318
|
+
* countBy counts every row, both for PROJECT_ID only. Returns the findBy
|
|
319
|
+
* reads as they happen.
|
|
320
|
+
*/
|
|
321
|
+
function serveRows(service: unknown, rows: Array<NamedRow>): Array<FindByArgs> {
|
|
322
|
+
const table: RowTable = service as RowTable;
|
|
323
|
+
const reads: Array<FindByArgs> = [];
|
|
324
|
+
|
|
325
|
+
jest
|
|
326
|
+
.spyOn(table, "findBy")
|
|
327
|
+
.mockImplementation(
|
|
328
|
+
async (findBy: FindByArgs): Promise<Array<NamedRow>> => {
|
|
329
|
+
reads.push(findBy);
|
|
330
|
+
|
|
331
|
+
if (!isThisProject(findBy.query)) {
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const skip: number = toNumber(findBy.skip);
|
|
336
|
+
|
|
337
|
+
return rows.slice(skip, skip + toNumber(findBy.limit));
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
jest
|
|
342
|
+
.spyOn(table, "countBy")
|
|
343
|
+
.mockImplementation(
|
|
344
|
+
async (countBy: CountByArgs): Promise<PositiveNumber> => {
|
|
345
|
+
return new PositiveNumber(
|
|
346
|
+
isThisProject(countBy.query) ? rows.length : 0,
|
|
347
|
+
);
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return reads;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function serveProject(data: {
|
|
355
|
+
monitors: Array<NamedRow>;
|
|
356
|
+
labels: Array<NamedRow>;
|
|
357
|
+
onCallDutyPolicies: Array<NamedRow>;
|
|
358
|
+
}): ProjectReads {
|
|
359
|
+
return {
|
|
360
|
+
severities: serveRows(IncidentSeverityService, SEVERITY_ROWS),
|
|
361
|
+
monitorStatuses: serveRows(MonitorStatusService, MONITOR_STATUS_ROWS),
|
|
362
|
+
monitors: serveRows(MonitorService, data.monitors),
|
|
363
|
+
labels: serveRows(LabelService, data.labels),
|
|
364
|
+
onCallDutyPolicies: serveRows(
|
|
365
|
+
OnCallDutyPolicyService,
|
|
366
|
+
data.onCallDutyPolicies,
|
|
367
|
+
),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function serveHugeProject(): ProjectReads {
|
|
372
|
+
return serveProject({
|
|
373
|
+
monitors: HUGE_MONITOR_ROWS,
|
|
374
|
+
labels: HUGE_LABEL_ROWS,
|
|
375
|
+
onCallDutyPolicies: HUGE_POLICY_ROWS,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function serveSmallProject(): ProjectReads {
|
|
380
|
+
return serveProject({
|
|
381
|
+
monitors: SMALL_MONITOR_ROWS,
|
|
382
|
+
labels: SMALL_LABEL_ROWS,
|
|
383
|
+
onCallDutyPolicies: SMALL_POLICY_ROWS,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// One read of the table, for this project, of the names and ids alone.
|
|
388
|
+
function expectOneRead(data: {
|
|
389
|
+
reads: Array<FindByArgs>;
|
|
390
|
+
sort: JSONObject;
|
|
391
|
+
limit: number;
|
|
392
|
+
}): void {
|
|
393
|
+
expect(data.reads).toHaveLength(1);
|
|
394
|
+
expect(data.reads[0]).toMatchObject({
|
|
395
|
+
query: { projectId: PROJECT_ID },
|
|
396
|
+
sort: data.sort,
|
|
397
|
+
limit: data.limit,
|
|
398
|
+
skip: 0,
|
|
399
|
+
props: { isRoot: true },
|
|
400
|
+
});
|
|
401
|
+
expect(data.reads[0]!.select).toEqual({ _id: true, name: true });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function bodyOf(card: JSONObject): Array<JSONObject> {
|
|
405
|
+
return card["body"] as Array<JSONObject>;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function actionsOf(card: JSONObject): Array<JSONObject> {
|
|
409
|
+
return card["actions"] as Array<JSONObject>;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function getActionTypes(card: JSONObject): Array<string> {
|
|
413
|
+
return actionsOf(card).map((action: JSONObject) => {
|
|
414
|
+
return action["type"] as string;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function getInput(card: JSONObject, id: string): JSONObject | undefined {
|
|
419
|
+
return bodyOf(card).find((element: JSONObject) => {
|
|
420
|
+
return element["id"] === id;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function getInputOrFail(card: JSONObject, id: string): JSONObject {
|
|
425
|
+
const input: JSONObject | undefined = getInput(card, id);
|
|
426
|
+
|
|
427
|
+
if (!input) {
|
|
428
|
+
throw new Error(`The card has no input "${id}"`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return input;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Each input as "<type> <id>", in card order.
|
|
435
|
+
function describeInputs(card: JSONObject): Array<string> {
|
|
436
|
+
return bodyOf(card)
|
|
437
|
+
.filter((element: JSONObject) => {
|
|
438
|
+
return typeof element["id"] === "string";
|
|
439
|
+
})
|
|
440
|
+
.map((element: JSONObject) => {
|
|
441
|
+
return `${element["type"] as string} ${element["id"] as string}`;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/*
|
|
446
|
+
* The card from top to bottom, one line per element: "<type> <id>" for an
|
|
447
|
+
* input, "note: <text>" for the small print and "heading: <text>" for the
|
|
448
|
+
* line that names the form.
|
|
449
|
+
*/
|
|
450
|
+
function layoutOf(card: JSONObject): Array<string> {
|
|
451
|
+
return bodyOf(card).map((element: JSONObject): string => {
|
|
452
|
+
if (typeof element["id"] === "string") {
|
|
453
|
+
return `${element["type"] as string} ${element["id"] as string}`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (element["type"] === "TextBlock") {
|
|
457
|
+
const kind: string = element["isSubtle"] === true ? "note" : "heading";
|
|
458
|
+
|
|
459
|
+
return `${kind}: ${element["text"] as string}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return element["type"] as string;
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function getChoices(
|
|
467
|
+
card: JSONObject,
|
|
468
|
+
id: string,
|
|
469
|
+
): Array<MicrosoftTeamsCardChoice> {
|
|
470
|
+
const input: JSONObject | undefined = getInput(card, id);
|
|
471
|
+
|
|
472
|
+
return input ? (input["choices"] as Array<MicrosoftTeamsCardChoice>) : [];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// The small print on the card: notes about lists, and the time zone.
|
|
476
|
+
function getNotes(card: JSONObject): Array<string> {
|
|
477
|
+
return bodyOf(card)
|
|
478
|
+
.filter((element: JSONObject) => {
|
|
479
|
+
return element["type"] === "TextBlock" && element["isSubtle"] === true;
|
|
480
|
+
})
|
|
481
|
+
.map((element: JSONObject) => {
|
|
482
|
+
return element["text"] as string;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function getSubmitAction(card: JSONObject): JSONObject {
|
|
487
|
+
const submitActions: Array<JSONObject> = actionsOf(card).filter(
|
|
488
|
+
(action: JSONObject) => {
|
|
489
|
+
return action["type"] === "Action.Submit";
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
expect(submitActions).toHaveLength(1);
|
|
494
|
+
|
|
495
|
+
return submitActions[0]!;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function getSubmitData(card: JSONObject): JSONObject {
|
|
499
|
+
return getSubmitAction(card)["data"] as JSONObject;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function getOpenUrlActions(card: JSONObject): Array<JSONObject> {
|
|
503
|
+
return actionsOf(card).filter((action: JSONObject) => {
|
|
504
|
+
return action["type"] === "Action.OpenUrl";
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/*
|
|
509
|
+
* The buttons under the form: the submit, then "Create in OneUptime" when a
|
|
510
|
+
* URL is expected (null: no such link).
|
|
511
|
+
*/
|
|
512
|
+
function expectCreateInOneUptimeLink(
|
|
513
|
+
card: JSONObject,
|
|
514
|
+
url: string | null,
|
|
515
|
+
): void {
|
|
516
|
+
if (url === null) {
|
|
517
|
+
expect(getActionTypes(card)).toEqual(["Action.Submit"]);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
expect(getActionTypes(card)).toEqual(["Action.Submit", "Action.OpenUrl"]);
|
|
522
|
+
expect(getOpenUrlActions(card)).toEqual([
|
|
523
|
+
{
|
|
524
|
+
type: "Action.OpenUrl",
|
|
525
|
+
title: "Create in OneUptime",
|
|
526
|
+
url: url,
|
|
527
|
+
},
|
|
528
|
+
]);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function sizeOf(card: JSONObject): number {
|
|
532
|
+
return MicrosoftTeamsMessageSize.getSizeInBytes(card);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/*
|
|
536
|
+
* Within the budget as Teams measures a message: in UTF-16 code units, two
|
|
537
|
+
* bytes each. Counted here as well as by getSizeInBytes, which the card
|
|
538
|
+
* builders fit to: a count in UTF-8 bytes, say, would pass a card twice as
|
|
539
|
+
* large as Teams sees it.
|
|
540
|
+
*/
|
|
541
|
+
function expectWithinBudget(card: JSONObject, budgetInBytes: number): void {
|
|
542
|
+
expect(JSON.stringify(card).length * 2).toBeLessThanOrEqual(budgetInBytes);
|
|
543
|
+
expect(sizeOf(card)).toBeLessThanOrEqual(budgetInBytes);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Text made of whole characters: every surrogate is one half of a pair.
|
|
547
|
+
const WHOLE_CHARACTERS_ONLY: RegExp =
|
|
548
|
+
/^(?:[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/;
|
|
549
|
+
|
|
550
|
+
function hasHalfAnEmoji(text: string): boolean {
|
|
551
|
+
return !WHOLE_CHARACTERS_ONLY.test(text);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Every text on the card that holds half of an emoji.
|
|
555
|
+
function getTextsWithHalfAnEmoji(value: unknown): Array<string> {
|
|
556
|
+
if (typeof value === "string") {
|
|
557
|
+
return hasHalfAnEmoji(value) ? [value] : [];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const texts: Array<string> = [];
|
|
561
|
+
|
|
562
|
+
if (value && typeof value === "object") {
|
|
563
|
+
for (const child of Object.values(value)) {
|
|
564
|
+
texts.push(...getTextsWithHalfAnEmoji(child));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return texts;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function showingNote(data: {
|
|
572
|
+
shown: number;
|
|
573
|
+
total: number;
|
|
574
|
+
noun: string;
|
|
575
|
+
hint: string;
|
|
576
|
+
}): string {
|
|
577
|
+
return `Showing the first ${data.shown} of ${data.total} ${data.noun}, by name. ${data.hint}`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function tooManyNote(data: {
|
|
581
|
+
total: number;
|
|
582
|
+
noun: string;
|
|
583
|
+
hint: string;
|
|
584
|
+
}): string {
|
|
585
|
+
return `This project has ${data.total} ${data.noun}, too many to list in Microsoft Teams. ${data.hint}`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/*
|
|
589
|
+
* The zone is the one the form was asked for from, and the times are read in
|
|
590
|
+
* it; it is not "your time zone" to someone else who fills the form in.
|
|
591
|
+
*/
|
|
592
|
+
function knownTimezoneNote(timezone: string): string {
|
|
593
|
+
return `Start and end times are in ${timezone}.`;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// A list the card shortens, and what its note must say.
|
|
597
|
+
type ShortenedList = {
|
|
598
|
+
inputId: string;
|
|
599
|
+
list: MicrosoftTeamsCardChoiceList;
|
|
600
|
+
total: number;
|
|
601
|
+
noun: string;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
/*
|
|
605
|
+
* Each shortened list shows its first choices (the note says "the first N ...,
|
|
606
|
+
* by name"), is still a usable list, and its note gives N as the number of
|
|
607
|
+
* choices actually on the card and the project's total. Returns the notes in
|
|
608
|
+
* card order.
|
|
609
|
+
*/
|
|
610
|
+
function expectShortenedLists(data: {
|
|
611
|
+
card: JSONObject;
|
|
612
|
+
lists: Array<ShortenedList>;
|
|
613
|
+
hint: string;
|
|
614
|
+
}): Array<string> {
|
|
615
|
+
const notes: Array<string> = [];
|
|
616
|
+
|
|
617
|
+
for (const shortened of data.lists) {
|
|
618
|
+
const shown: Array<MicrosoftTeamsCardChoice> = getChoices(
|
|
619
|
+
data.card,
|
|
620
|
+
shortened.inputId,
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
expect(shown.length).toBeGreaterThan(0);
|
|
624
|
+
expect(shown.length).toBeLessThan(shortened.total);
|
|
625
|
+
expect(shown).toEqual(shortened.list.choices.slice(0, shown.length));
|
|
626
|
+
|
|
627
|
+
notes.push(
|
|
628
|
+
showingNote({
|
|
629
|
+
shown: shown.length,
|
|
630
|
+
total: shortened.total,
|
|
631
|
+
noun: shortened.noun,
|
|
632
|
+
hint: data.hint,
|
|
633
|
+
}),
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return notes;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function expectHugeIncidentCard(data: {
|
|
641
|
+
card: JSONObject;
|
|
642
|
+
choices: MicrosoftTeamsNewIncidentFormChoices;
|
|
643
|
+
budgetInBytes: number;
|
|
644
|
+
// The "Create in OneUptime" link the card offers, or null for none.
|
|
645
|
+
createInOneUptimeUrl: string | null;
|
|
646
|
+
}): void {
|
|
647
|
+
const { card, choices, budgetInBytes } = data;
|
|
648
|
+
|
|
649
|
+
expect(card["version"]).toBe("1.5");
|
|
650
|
+
expectWithinBudget(card, budgetInBytes);
|
|
651
|
+
// Shortened, not stripped: the card still uses most of its budget.
|
|
652
|
+
expect(sizeOf(card)).toBeGreaterThan(budgetInBytes / 2);
|
|
653
|
+
|
|
654
|
+
// Required, so never shortened.
|
|
655
|
+
expect(getChoices(card, "incidentSeverity")).toEqual(
|
|
656
|
+
choices.severities.choices,
|
|
657
|
+
);
|
|
658
|
+
// Shown whole whenever monitors are shown.
|
|
659
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
660
|
+
choices.monitorStatuses.choices,
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
const [monitorsNote, policiesNote, labelsNote]: Array<string> =
|
|
664
|
+
expectShortenedLists({
|
|
665
|
+
card: card,
|
|
666
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
667
|
+
lists: [
|
|
668
|
+
{
|
|
669
|
+
inputId: "incidentMonitors",
|
|
670
|
+
list: choices.monitors,
|
|
671
|
+
total: HUGE_MONITOR_COUNT,
|
|
672
|
+
noun: "monitors",
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
inputId: "onCallDutyPolicies",
|
|
676
|
+
list: choices.onCallDutyPolicies,
|
|
677
|
+
total: HUGE_POLICY_COUNT,
|
|
678
|
+
noun: "on-call policies",
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
inputId: "labels",
|
|
682
|
+
list: choices.labels,
|
|
683
|
+
total: HUGE_LABEL_COUNT,
|
|
684
|
+
noun: "labels",
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Each note right under the list it is about.
|
|
690
|
+
expect(layoutOf(card)).toEqual([
|
|
691
|
+
"heading: Create New Incident",
|
|
692
|
+
"Input.Text incidentTitle",
|
|
693
|
+
"Input.Text incidentDescription",
|
|
694
|
+
"Input.ChoiceSet incidentSeverity",
|
|
695
|
+
"Input.ChoiceSet incidentMonitors",
|
|
696
|
+
`note: ${monitorsNote}`,
|
|
697
|
+
"Input.ChoiceSet monitorStatus",
|
|
698
|
+
"Input.ChoiceSet onCallDutyPolicies",
|
|
699
|
+
`note: ${policiesNote}`,
|
|
700
|
+
"Input.ChoiceSet labels",
|
|
701
|
+
`note: ${labelsNote}`,
|
|
702
|
+
]);
|
|
703
|
+
|
|
704
|
+
expectCreateInOneUptimeLink(card, data.createInOneUptimeUrl);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function expectHugeMaintenanceCard(data: {
|
|
708
|
+
card: JSONObject;
|
|
709
|
+
choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices;
|
|
710
|
+
budgetInBytes: number;
|
|
711
|
+
timezoneNote: string;
|
|
712
|
+
// The "Create in OneUptime" link the card offers, or null for none.
|
|
713
|
+
createInOneUptimeUrl: string | null;
|
|
714
|
+
}): void {
|
|
715
|
+
const { card, choices, budgetInBytes } = data;
|
|
716
|
+
|
|
717
|
+
expect(card["version"]).toBe("1.5");
|
|
718
|
+
expectWithinBudget(card, budgetInBytes);
|
|
719
|
+
expect(sizeOf(card)).toBeGreaterThan(budgetInBytes / 2);
|
|
720
|
+
|
|
721
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
722
|
+
choices.monitorStatuses.choices,
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
const [monitorsNote, labelsNote]: Array<string> = expectShortenedLists({
|
|
726
|
+
card: card,
|
|
727
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
728
|
+
lists: [
|
|
729
|
+
{
|
|
730
|
+
inputId: "scheduledMaintenanceMonitors",
|
|
731
|
+
list: choices.monitors,
|
|
732
|
+
total: HUGE_MONITOR_COUNT,
|
|
733
|
+
noun: "monitors",
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
inputId: "labels",
|
|
737
|
+
list: choices.labels,
|
|
738
|
+
total: HUGE_LABEL_COUNT,
|
|
739
|
+
noun: "labels",
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// The time zone just above the dates, each list's note right under it.
|
|
745
|
+
expect(layoutOf(card)).toEqual([
|
|
746
|
+
"heading: Create New Scheduled Maintenance",
|
|
747
|
+
"Input.Text scheduledMaintenanceTitle",
|
|
748
|
+
"Input.Text scheduledMaintenanceDescription",
|
|
749
|
+
`note: ${data.timezoneNote}`,
|
|
750
|
+
"Input.Date startDate",
|
|
751
|
+
"Input.Time startTime",
|
|
752
|
+
"Input.Date endDate",
|
|
753
|
+
"Input.Time endTime",
|
|
754
|
+
"Input.ChoiceSet scheduledMaintenanceMonitors",
|
|
755
|
+
`note: ${monitorsNote}`,
|
|
756
|
+
"Input.ChoiceSet monitorStatus",
|
|
757
|
+
"Input.ChoiceSet labels",
|
|
758
|
+
`note: ${labelsNote}`,
|
|
759
|
+
]);
|
|
760
|
+
|
|
761
|
+
expectCreateInOneUptimeLink(card, data.createInOneUptimeUrl);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function shownCounts(card: JSONObject, inputIds: Array<string>): Array<number> {
|
|
765
|
+
return inputIds.map((inputId: string) => {
|
|
766
|
+
return getChoices(card, inputId).length;
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
afterEach((): void => {
|
|
771
|
+
jest.restoreAllMocks();
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
describe("MicrosoftTeamsIncidentActions.getNewIncidentFormChoices", () => {
|
|
775
|
+
test("reads each list through MicrosoftTeamsCardChoices, for the given project", async () => {
|
|
776
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
777
|
+
smallIncidentChoices();
|
|
778
|
+
|
|
779
|
+
const spies: Array<ReturnType<typeof jest.spyOn>> = [
|
|
780
|
+
jest
|
|
781
|
+
.spyOn(MicrosoftTeamsCardChoices, "getIncidentSeverityChoices")
|
|
782
|
+
.mockResolvedValue(choices.severities),
|
|
783
|
+
jest
|
|
784
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorChoices")
|
|
785
|
+
.mockResolvedValue(choices.monitors),
|
|
786
|
+
jest
|
|
787
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorStatusChoices")
|
|
788
|
+
.mockResolvedValue(choices.monitorStatuses),
|
|
789
|
+
jest
|
|
790
|
+
.spyOn(MicrosoftTeamsCardChoices, "getLabelChoices")
|
|
791
|
+
.mockResolvedValue(choices.labels),
|
|
792
|
+
jest
|
|
793
|
+
.spyOn(MicrosoftTeamsCardChoices, "getOnCallDutyPolicyChoices")
|
|
794
|
+
.mockResolvedValue(choices.onCallDutyPolicies),
|
|
795
|
+
];
|
|
796
|
+
|
|
797
|
+
const result: MicrosoftTeamsNewIncidentFormChoices =
|
|
798
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(PROJECT_ID);
|
|
799
|
+
|
|
800
|
+
for (const spy of spies) {
|
|
801
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
802
|
+
expect(spy).toHaveBeenCalledWith(PROJECT_ID);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Each list under its own key: a swap would offer monitors as severities.
|
|
806
|
+
expect(result.severities).toBe(choices.severities);
|
|
807
|
+
expect(result.monitors).toBe(choices.monitors);
|
|
808
|
+
expect(result.monitorStatuses).toBe(choices.monitorStatuses);
|
|
809
|
+
expect(result.labels).toBe(choices.labels);
|
|
810
|
+
expect(result.onCallDutyPolicies).toBe(choices.onCallDutyPolicies);
|
|
811
|
+
expect(Object.keys(result).sort()).toEqual([
|
|
812
|
+
"labels",
|
|
813
|
+
"monitorStatuses",
|
|
814
|
+
"monitors",
|
|
815
|
+
"onCallDutyPolicies",
|
|
816
|
+
"severities",
|
|
817
|
+
]);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("fails when a list cannot be read, so the command can say so", async () => {
|
|
821
|
+
jest
|
|
822
|
+
.spyOn(MicrosoftTeamsCardChoices, "getIncidentSeverityChoices")
|
|
823
|
+
.mockResolvedValue(listOf(SEVERITY_ROWS));
|
|
824
|
+
jest
|
|
825
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorChoices")
|
|
826
|
+
.mockResolvedValue(listOf(SMALL_MONITOR_ROWS));
|
|
827
|
+
jest
|
|
828
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorStatusChoices")
|
|
829
|
+
.mockResolvedValue(listOf(MONITOR_STATUS_ROWS));
|
|
830
|
+
jest
|
|
831
|
+
.spyOn(MicrosoftTeamsCardChoices, "getLabelChoices")
|
|
832
|
+
.mockRejectedValue(new Error("Connection terminated unexpectedly"));
|
|
833
|
+
jest
|
|
834
|
+
.spyOn(MicrosoftTeamsCardChoices, "getOnCallDutyPolicyChoices")
|
|
835
|
+
.mockResolvedValue(listOf(SMALL_POLICY_ROWS));
|
|
836
|
+
|
|
837
|
+
await expect(
|
|
838
|
+
MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(PROJECT_ID),
|
|
839
|
+
).rejects.toThrow("Connection terminated unexpectedly");
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
test("reads a huge project as capped lists that still know the project's totals", async () => {
|
|
843
|
+
serveHugeProject();
|
|
844
|
+
|
|
845
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
846
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(PROJECT_ID);
|
|
847
|
+
|
|
848
|
+
expect(choices.monitors.choices).toHaveLength(
|
|
849
|
+
MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
850
|
+
);
|
|
851
|
+
expect(choices.monitors.totalCount).toBe(HUGE_MONITOR_COUNT);
|
|
852
|
+
expect(choices.labels.choices).toHaveLength(
|
|
853
|
+
MICROSOFT_TEAMS_MAX_LABEL_CHOICES,
|
|
854
|
+
);
|
|
855
|
+
expect(choices.labels.totalCount).toBe(HUGE_LABEL_COUNT);
|
|
856
|
+
expect(choices.onCallDutyPolicies.choices).toHaveLength(
|
|
857
|
+
MICROSOFT_TEAMS_MAX_ON_CALL_POLICY_CHOICES,
|
|
858
|
+
);
|
|
859
|
+
expect(choices.onCallDutyPolicies.totalCount).toBe(HUGE_POLICY_COUNT);
|
|
860
|
+
expect(choices.severities.choices).toHaveLength(5);
|
|
861
|
+
expect(choices.monitorStatuses.choices).toHaveLength(5);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("reads each list once, capped, sorted as the card says, names and ids only", async () => {
|
|
865
|
+
const reads: ProjectReads = serveHugeProject();
|
|
866
|
+
|
|
867
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(PROJECT_ID);
|
|
868
|
+
|
|
869
|
+
// The notes under these three say "Showing the first N of M ..., by name".
|
|
870
|
+
expectOneRead({
|
|
871
|
+
reads: reads.monitors,
|
|
872
|
+
sort: { name: SortOrder.Ascending },
|
|
873
|
+
limit: MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
874
|
+
});
|
|
875
|
+
expectOneRead({
|
|
876
|
+
reads: reads.labels,
|
|
877
|
+
sort: { name: SortOrder.Ascending },
|
|
878
|
+
limit: MICROSOFT_TEAMS_MAX_LABEL_CHOICES,
|
|
879
|
+
});
|
|
880
|
+
expectOneRead({
|
|
881
|
+
reads: reads.onCallDutyPolicies,
|
|
882
|
+
sort: { name: SortOrder.Ascending },
|
|
883
|
+
limit: MICROSOFT_TEAMS_MAX_ON_CALL_POLICY_CHOICES,
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// As the project orders them everywhere else.
|
|
887
|
+
expectOneRead({
|
|
888
|
+
reads: reads.severities,
|
|
889
|
+
sort: { order: SortOrder.Ascending },
|
|
890
|
+
limit: MICROSOFT_TEAMS_MAX_SEVERITY_CHOICES,
|
|
891
|
+
});
|
|
892
|
+
expectOneRead({
|
|
893
|
+
reads: reads.monitorStatuses,
|
|
894
|
+
sort: { priority: SortOrder.Ascending },
|
|
895
|
+
limit: MICROSOFT_TEAMS_MAX_MONITOR_STATUS_CHOICES,
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
describe("MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices", () => {
|
|
901
|
+
test("reads the monitor, monitor status and label lists for the given project, and nothing else", async () => {
|
|
902
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
903
|
+
smallMaintenanceChoices();
|
|
904
|
+
|
|
905
|
+
const spies: Array<ReturnType<typeof jest.spyOn>> = [
|
|
906
|
+
jest
|
|
907
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorChoices")
|
|
908
|
+
.mockResolvedValue(choices.monitors),
|
|
909
|
+
jest
|
|
910
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorStatusChoices")
|
|
911
|
+
.mockResolvedValue(choices.monitorStatuses),
|
|
912
|
+
jest
|
|
913
|
+
.spyOn(MicrosoftTeamsCardChoices, "getLabelChoices")
|
|
914
|
+
.mockResolvedValue(choices.labels),
|
|
915
|
+
];
|
|
916
|
+
const severitySpy: ReturnType<typeof jest.spyOn> = jest
|
|
917
|
+
.spyOn(MicrosoftTeamsCardChoices, "getIncidentSeverityChoices")
|
|
918
|
+
.mockResolvedValue(listOf(SEVERITY_ROWS));
|
|
919
|
+
const policySpy: ReturnType<typeof jest.spyOn> = jest
|
|
920
|
+
.spyOn(MicrosoftTeamsCardChoices, "getOnCallDutyPolicyChoices")
|
|
921
|
+
.mockResolvedValue(listOf(SMALL_POLICY_ROWS));
|
|
922
|
+
|
|
923
|
+
const result: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
924
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
925
|
+
PROJECT_ID,
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
for (const spy of spies) {
|
|
929
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
930
|
+
expect(spy).toHaveBeenCalledWith(PROJECT_ID);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// A maintenance event has no severity and pages no on-call policy.
|
|
934
|
+
expect(severitySpy).not.toHaveBeenCalled();
|
|
935
|
+
expect(policySpy).not.toHaveBeenCalled();
|
|
936
|
+
|
|
937
|
+
expect(result.monitors).toBe(choices.monitors);
|
|
938
|
+
expect(result.monitorStatuses).toBe(choices.monitorStatuses);
|
|
939
|
+
expect(result.labels).toBe(choices.labels);
|
|
940
|
+
expect(Object.keys(result).sort()).toEqual([
|
|
941
|
+
"labels",
|
|
942
|
+
"monitorStatuses",
|
|
943
|
+
"monitors",
|
|
944
|
+
]);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
test("fails when a list cannot be read, so the command can say so", async () => {
|
|
948
|
+
jest
|
|
949
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorChoices")
|
|
950
|
+
.mockRejectedValue(new Error("Connection terminated unexpectedly"));
|
|
951
|
+
jest
|
|
952
|
+
.spyOn(MicrosoftTeamsCardChoices, "getMonitorStatusChoices")
|
|
953
|
+
.mockResolvedValue(listOf(MONITOR_STATUS_ROWS));
|
|
954
|
+
jest
|
|
955
|
+
.spyOn(MicrosoftTeamsCardChoices, "getLabelChoices")
|
|
956
|
+
.mockResolvedValue(listOf(SMALL_LABEL_ROWS));
|
|
957
|
+
|
|
958
|
+
await expect(
|
|
959
|
+
MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
960
|
+
PROJECT_ID,
|
|
961
|
+
),
|
|
962
|
+
).rejects.toThrow("Connection terminated unexpectedly");
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
test("reads a huge project as capped lists that still know the project's totals", async () => {
|
|
966
|
+
const reads: ProjectReads = serveHugeProject();
|
|
967
|
+
|
|
968
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
969
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
970
|
+
PROJECT_ID,
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
expect(choices.monitors.choices).toHaveLength(
|
|
974
|
+
MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
975
|
+
);
|
|
976
|
+
expect(choices.monitors.totalCount).toBe(HUGE_MONITOR_COUNT);
|
|
977
|
+
expect(choices.labels.choices).toHaveLength(
|
|
978
|
+
MICROSOFT_TEAMS_MAX_LABEL_CHOICES,
|
|
979
|
+
);
|
|
980
|
+
expect(choices.labels.totalCount).toBe(HUGE_LABEL_COUNT);
|
|
981
|
+
expect(choices.monitorStatuses.choices).toHaveLength(5);
|
|
982
|
+
|
|
983
|
+
expect(reads.severities).toEqual([]);
|
|
984
|
+
expect(reads.onCallDutyPolicies).toEqual([]);
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
describe("MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget, small project", () => {
|
|
989
|
+
test.each(NON_ZERO_BUDGETS)(
|
|
990
|
+
"lists every choice within %i bytes, with no notes and no extra link",
|
|
991
|
+
(budget: number) => {
|
|
992
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
993
|
+
smallIncidentChoices();
|
|
994
|
+
|
|
995
|
+
const card: JSONObject =
|
|
996
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
997
|
+
choices: choices,
|
|
998
|
+
budgetInBytes: budget,
|
|
999
|
+
// Nothing is left off, so the link is not offered.
|
|
1000
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
expect(card["type"]).toBe("AdaptiveCard");
|
|
1004
|
+
expect(card["version"]).toBe("1.5");
|
|
1005
|
+
expect(card["$schema"]).toBe(
|
|
1006
|
+
"http://adaptivecards.io/schemas/adaptive-card.json",
|
|
1007
|
+
);
|
|
1008
|
+
expectWithinBudget(card, budget);
|
|
1009
|
+
|
|
1010
|
+
expect(getChoices(card, "incidentSeverity")).toEqual(
|
|
1011
|
+
choices.severities.choices,
|
|
1012
|
+
);
|
|
1013
|
+
expect(getChoices(card, "incidentMonitors")).toEqual(
|
|
1014
|
+
choices.monitors.choices,
|
|
1015
|
+
);
|
|
1016
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
1017
|
+
choices.monitorStatuses.choices,
|
|
1018
|
+
);
|
|
1019
|
+
expect(getChoices(card, "onCallDutyPolicies")).toEqual(
|
|
1020
|
+
choices.onCallDutyPolicies.choices,
|
|
1021
|
+
);
|
|
1022
|
+
expect(getChoices(card, "labels")).toEqual(choices.labels.choices);
|
|
1023
|
+
|
|
1024
|
+
expect(getNotes(card)).toEqual([]);
|
|
1025
|
+
expect(getOpenUrlActions(card)).toEqual([]);
|
|
1026
|
+
expect(getActionTypes(card)).toEqual(["Action.Submit"]);
|
|
1027
|
+
},
|
|
1028
|
+
);
|
|
1029
|
+
|
|
1030
|
+
test("keeps the input ids and the submit action the submit handler reads", () => {
|
|
1031
|
+
const card: JSONObject =
|
|
1032
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1033
|
+
choices: smallIncidentChoices(),
|
|
1034
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
expect(describeInputs(card)).toEqual([
|
|
1038
|
+
"Input.Text incidentTitle",
|
|
1039
|
+
"Input.Text incidentDescription",
|
|
1040
|
+
"Input.ChoiceSet incidentSeverity",
|
|
1041
|
+
"Input.ChoiceSet incidentMonitors",
|
|
1042
|
+
"Input.ChoiceSet monitorStatus",
|
|
1043
|
+
"Input.ChoiceSet onCallDutyPolicies",
|
|
1044
|
+
"Input.ChoiceSet labels",
|
|
1045
|
+
]);
|
|
1046
|
+
|
|
1047
|
+
expect(getSubmitAction(card)).toEqual({
|
|
1048
|
+
type: "Action.Submit",
|
|
1049
|
+
title: "Create Incident",
|
|
1050
|
+
data: { action: "SubmitNewIncident" },
|
|
1051
|
+
});
|
|
1052
|
+
expect(getSubmitData(card)["action"]).toBe(
|
|
1053
|
+
MicrosoftTeamsIncidentActionType.SubmitNewIncident,
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
// Required inputs; the submit handler refuses a submit without them.
|
|
1057
|
+
for (const requiredId of [
|
|
1058
|
+
"incidentTitle",
|
|
1059
|
+
"incidentDescription",
|
|
1060
|
+
"incidentSeverity",
|
|
1061
|
+
]) {
|
|
1062
|
+
expect(getInputOrFail(card, requiredId)["isRequired"]).toBe(true);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Many ids arrive comma separated; one severity and one status arrive alone.
|
|
1066
|
+
for (const multiSelectId of [
|
|
1067
|
+
"incidentMonitors",
|
|
1068
|
+
"onCallDutyPolicies",
|
|
1069
|
+
"labels",
|
|
1070
|
+
]) {
|
|
1071
|
+
expect(getInputOrFail(card, multiSelectId)["isMultiSelect"]).toBe(true);
|
|
1072
|
+
}
|
|
1073
|
+
for (const singleSelectId of ["incidentSeverity", "monitorStatus"]) {
|
|
1074
|
+
expect(
|
|
1075
|
+
getInputOrFail(card, singleSelectId)["isMultiSelect"],
|
|
1076
|
+
).toBeUndefined();
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
expect(getInputOrFail(card, "incidentDescription")["isMultiline"]).toBe(
|
|
1080
|
+
true,
|
|
1081
|
+
);
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
describe("MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget, small project", () => {
|
|
1086
|
+
test.each(NON_ZERO_BUDGETS)(
|
|
1087
|
+
"lists every choice within %i bytes, with only the time zone note and no extra link",
|
|
1088
|
+
(budget: number) => {
|
|
1089
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1090
|
+
smallMaintenanceChoices();
|
|
1091
|
+
|
|
1092
|
+
const card: JSONObject =
|
|
1093
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1094
|
+
{
|
|
1095
|
+
choices: choices,
|
|
1096
|
+
budgetInBytes: budget,
|
|
1097
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1098
|
+
},
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
expect(card["type"]).toBe("AdaptiveCard");
|
|
1102
|
+
expect(card["version"]).toBe("1.5");
|
|
1103
|
+
expect(card["$schema"]).toBe(
|
|
1104
|
+
"http://adaptivecards.io/schemas/adaptive-card.json",
|
|
1105
|
+
);
|
|
1106
|
+
expectWithinBudget(card, budget);
|
|
1107
|
+
|
|
1108
|
+
expect(getChoices(card, "scheduledMaintenanceMonitors")).toEqual(
|
|
1109
|
+
choices.monitors.choices,
|
|
1110
|
+
);
|
|
1111
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
1112
|
+
choices.monitorStatuses.choices,
|
|
1113
|
+
);
|
|
1114
|
+
expect(getChoices(card, "labels")).toEqual(choices.labels.choices);
|
|
1115
|
+
|
|
1116
|
+
expect(getNotes(card)).toEqual([GENERIC_TIMEZONE_NOTE]);
|
|
1117
|
+
expect(getOpenUrlActions(card)).toEqual([]);
|
|
1118
|
+
expect(getActionTypes(card)).toEqual(["Action.Submit"]);
|
|
1119
|
+
},
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
test("keeps the input ids and the submit action the submit handler reads", () => {
|
|
1123
|
+
const card: JSONObject =
|
|
1124
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1125
|
+
{
|
|
1126
|
+
choices: smallMaintenanceChoices(),
|
|
1127
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1128
|
+
},
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
expect(describeInputs(card)).toEqual([
|
|
1132
|
+
"Input.Text scheduledMaintenanceTitle",
|
|
1133
|
+
"Input.Text scheduledMaintenanceDescription",
|
|
1134
|
+
"Input.Date startDate",
|
|
1135
|
+
"Input.Time startTime",
|
|
1136
|
+
"Input.Date endDate",
|
|
1137
|
+
"Input.Time endTime",
|
|
1138
|
+
"Input.ChoiceSet scheduledMaintenanceMonitors",
|
|
1139
|
+
"Input.ChoiceSet monitorStatus",
|
|
1140
|
+
"Input.ChoiceSet labels",
|
|
1141
|
+
]);
|
|
1142
|
+
|
|
1143
|
+
expect(getSubmitAction(card)).toEqual({
|
|
1144
|
+
type: "Action.Submit",
|
|
1145
|
+
title: "Create Maintenance Event",
|
|
1146
|
+
data: { action: "SubmitNewScheduledMaintenance" },
|
|
1147
|
+
});
|
|
1148
|
+
expect(getSubmitData(card)["action"]).toBe(
|
|
1149
|
+
MicrosoftTeamsScheduledMaintenanceActionType.SubmitNewScheduledMaintenance,
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
for (const requiredId of [
|
|
1153
|
+
"scheduledMaintenanceTitle",
|
|
1154
|
+
"scheduledMaintenanceDescription",
|
|
1155
|
+
"startDate",
|
|
1156
|
+
"startTime",
|
|
1157
|
+
"endDate",
|
|
1158
|
+
"endTime",
|
|
1159
|
+
]) {
|
|
1160
|
+
expect(getInputOrFail(card, requiredId)["isRequired"]).toBe(true);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
for (const multiSelectId of ["scheduledMaintenanceMonitors", "labels"]) {
|
|
1164
|
+
expect(getInputOrFail(card, multiSelectId)["isMultiSelect"]).toBe(true);
|
|
1165
|
+
}
|
|
1166
|
+
expect(
|
|
1167
|
+
getInputOrFail(card, "monitorStatus")["isMultiSelect"],
|
|
1168
|
+
).toBeUndefined();
|
|
1169
|
+
|
|
1170
|
+
// Nothing incident-only on this form.
|
|
1171
|
+
expect(getInput(card, "incidentSeverity")).toBeUndefined();
|
|
1172
|
+
expect(getInput(card, "onCallDutyPolicies")).toBeUndefined();
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
test("names the time zone right above the start and end inputs", () => {
|
|
1176
|
+
const card: JSONObject =
|
|
1177
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1178
|
+
{
|
|
1179
|
+
choices: smallMaintenanceChoices(),
|
|
1180
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1181
|
+
timezone: "America/New_York",
|
|
1182
|
+
},
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
const body: Array<JSONObject> = bodyOf(card);
|
|
1186
|
+
const startDateIndex: number = body.findIndex((element: JSONObject) => {
|
|
1187
|
+
return element["id"] === "startDate";
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
expect(startDateIndex).toBeGreaterThan(0);
|
|
1191
|
+
expect(body[startDateIndex - 1]).toMatchObject({
|
|
1192
|
+
type: "TextBlock",
|
|
1193
|
+
text: knownTimezoneNote("America/New_York"),
|
|
1194
|
+
wrap: true,
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
describe("the submit data never collides with an input", () => {
|
|
1200
|
+
/*
|
|
1201
|
+
* Teams merges the Action.Submit data with the input values into one
|
|
1202
|
+
* object, so an input named like a data key would overwrite it.
|
|
1203
|
+
*/
|
|
1204
|
+
test("incident form", () => {
|
|
1205
|
+
const card: JSONObject =
|
|
1206
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1207
|
+
choices: smallIncidentChoices(),
|
|
1208
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
for (const key of Object.keys(getSubmitData(card))) {
|
|
1212
|
+
expect(getInput(card, key)).toBeUndefined();
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
test("maintenance form, with the time zone in its data", () => {
|
|
1217
|
+
const card: JSONObject =
|
|
1218
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1219
|
+
{
|
|
1220
|
+
choices: smallMaintenanceChoices(),
|
|
1221
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1222
|
+
timezone: "Europe/Berlin",
|
|
1223
|
+
},
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
expect(Object.keys(getSubmitData(card)).sort()).toEqual([
|
|
1227
|
+
"action",
|
|
1228
|
+
"timezone",
|
|
1229
|
+
]);
|
|
1230
|
+
|
|
1231
|
+
for (const key of Object.keys(getSubmitData(card))) {
|
|
1232
|
+
expect(getInput(card, key)).toBeUndefined();
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
describe("MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget, huge project (#4111)", () => {
|
|
1238
|
+
test.each(NON_ZERO_BUDGETS)(
|
|
1239
|
+
"fits %i bytes with the lists as the fetchers read them, and says what it left off",
|
|
1240
|
+
async (budget: number) => {
|
|
1241
|
+
serveHugeProject();
|
|
1242
|
+
|
|
1243
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1244
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(
|
|
1245
|
+
PROJECT_ID,
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
const card: JSONObject =
|
|
1249
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1250
|
+
choices: choices,
|
|
1251
|
+
budgetInBytes: budget,
|
|
1252
|
+
initialTitle: "Checkout is down",
|
|
1253
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
expectHugeIncidentCard({
|
|
1257
|
+
card: card,
|
|
1258
|
+
choices: choices,
|
|
1259
|
+
budgetInBytes: budget,
|
|
1260
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1261
|
+
});
|
|
1262
|
+
expect(getInputOrFail(card, "incidentTitle")["value"]).toBe(
|
|
1263
|
+
"Checkout is down",
|
|
1264
|
+
);
|
|
1265
|
+
},
|
|
1266
|
+
);
|
|
1267
|
+
|
|
1268
|
+
test.each(NON_ZERO_BUDGETS)(
|
|
1269
|
+
"fits %i bytes even when handed every monitor, label and policy of the project",
|
|
1270
|
+
(budget: number) => {
|
|
1271
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1272
|
+
wholeHugeIncidentChoices();
|
|
1273
|
+
|
|
1274
|
+
const card: JSONObject =
|
|
1275
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1276
|
+
choices: choices,
|
|
1277
|
+
budgetInBytes: budget,
|
|
1278
|
+
// The longest title the form takes counts against the budget too.
|
|
1279
|
+
initialTitle: "t".repeat(INCIDENT_TITLE_MAX_LENGTH),
|
|
1280
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
expectHugeIncidentCard({
|
|
1284
|
+
card: card,
|
|
1285
|
+
choices: choices,
|
|
1286
|
+
budgetInBytes: budget,
|
|
1287
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1288
|
+
});
|
|
1289
|
+
},
|
|
1290
|
+
);
|
|
1291
|
+
|
|
1292
|
+
test("the old card, with every choice, is far over what Teams takes", () => {
|
|
1293
|
+
const card: JSONObject =
|
|
1294
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1295
|
+
choices: wholeHugeIncidentChoices(),
|
|
1296
|
+
budgetInBytes: Number.MAX_SAFE_INTEGER,
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
expect(getChoices(card, "incidentMonitors")).toHaveLength(
|
|
1300
|
+
HUGE_MONITOR_COUNT,
|
|
1301
|
+
);
|
|
1302
|
+
// Teams refuses a message over about 100 KB.
|
|
1303
|
+
expect(sizeOf(card)).toBeGreaterThan(100 * 1024);
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
/*
|
|
1307
|
+
* No dashboard URL could be built (null), none was given (undefined), or
|
|
1308
|
+
* it came out empty: an Action.OpenUrl needs somewhere to go. The card is
|
|
1309
|
+
* otherwise the same, and still says what it left off.
|
|
1310
|
+
*/
|
|
1311
|
+
test.each([null, undefined, ""])(
|
|
1312
|
+
"offers no Create in OneUptime link without a URL (%p), at any budget",
|
|
1313
|
+
async (createInOneUptimeUrl: string | null | undefined) => {
|
|
1314
|
+
serveHugeProject();
|
|
1315
|
+
|
|
1316
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1317
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(
|
|
1318
|
+
PROJECT_ID,
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1321
|
+
for (const budget of NON_ZERO_BUDGETS) {
|
|
1322
|
+
expectHugeIncidentCard({
|
|
1323
|
+
card: MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1324
|
+
choices: choices,
|
|
1325
|
+
budgetInBytes: budget,
|
|
1326
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
1327
|
+
}),
|
|
1328
|
+
choices: choices,
|
|
1329
|
+
budgetInBytes: budget,
|
|
1330
|
+
createInOneUptimeUrl: null,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const budgetZeroCard: JSONObject =
|
|
1335
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1336
|
+
choices: choices,
|
|
1337
|
+
budgetInBytes: 0,
|
|
1338
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
expect(getNotes(budgetZeroCard)).toHaveLength(3);
|
|
1342
|
+
expectCreateInOneUptimeLink(budgetZeroCard, null);
|
|
1343
|
+
},
|
|
1344
|
+
);
|
|
1345
|
+
|
|
1346
|
+
test("a smaller budget shows no more of any list and makes a smaller card", () => {
|
|
1347
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1348
|
+
wholeHugeIncidentChoices();
|
|
1349
|
+
const trimmableIds: Array<string> = [
|
|
1350
|
+
"incidentMonitors",
|
|
1351
|
+
"onCallDutyPolicies",
|
|
1352
|
+
"labels",
|
|
1353
|
+
];
|
|
1354
|
+
|
|
1355
|
+
const cards: Array<JSONObject> =
|
|
1356
|
+
MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES.map((budget: number) => {
|
|
1357
|
+
return MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1358
|
+
choices: choices,
|
|
1359
|
+
budgetInBytes: budget,
|
|
1360
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1361
|
+
});
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
for (let index: number = 1; index < cards.length; index++) {
|
|
1365
|
+
const larger: JSONObject = cards[index - 1]!;
|
|
1366
|
+
const smaller: JSONObject = cards[index]!;
|
|
1367
|
+
|
|
1368
|
+
/*
|
|
1369
|
+
* A card refused as too large is followed by a smaller one, never by the
|
|
1370
|
+
* same card again.
|
|
1371
|
+
*/
|
|
1372
|
+
expect(sizeOf(smaller)).toBeLessThan(sizeOf(larger));
|
|
1373
|
+
|
|
1374
|
+
const largerCounts: Array<number> = shownCounts(larger, trimmableIds);
|
|
1375
|
+
const smallerCounts: Array<number> = shownCounts(smaller, trimmableIds);
|
|
1376
|
+
|
|
1377
|
+
smallerCounts.forEach((count: number, listIndex: number) => {
|
|
1378
|
+
expect(count).toBeLessThanOrEqual(largerCounts[listIndex]!);
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
test("leaves the choices it was given alone, so every budget starts from the same lists", () => {
|
|
1384
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1385
|
+
wholeHugeIncidentChoices();
|
|
1386
|
+
const before: string = JSON.stringify(choices);
|
|
1387
|
+
|
|
1388
|
+
const build: (budget: number) => JSONObject = (
|
|
1389
|
+
budget: number,
|
|
1390
|
+
): JSONObject => {
|
|
1391
|
+
return MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1392
|
+
choices: choices,
|
|
1393
|
+
budgetInBytes: budget,
|
|
1394
|
+
initialTitle: "Checkout is down",
|
|
1395
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1396
|
+
});
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
const first: JSONObject = build(FIRST_BUDGET);
|
|
1400
|
+
|
|
1401
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
1402
|
+
build(budget);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
expect(JSON.stringify(choices)).toBe(before);
|
|
1406
|
+
expect(build(FIRST_BUDGET)).toEqual(first);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
test("never shortens the severities, even when they are what makes the card big", () => {
|
|
1410
|
+
const severities: MicrosoftTeamsCardChoiceList = listOf(
|
|
1411
|
+
buildRows({
|
|
1412
|
+
table: 9,
|
|
1413
|
+
count: MICROSOFT_TEAMS_MAX_SEVERITY_CHOICES,
|
|
1414
|
+
nameOf: (index: number): string => {
|
|
1415
|
+
return `Severity ${padded(index)} ${"s".repeat(80)}`.substring(0, 80);
|
|
1416
|
+
},
|
|
1417
|
+
}),
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
1421
|
+
const card: JSONObject =
|
|
1422
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1423
|
+
choices: { ...wholeHugeIncidentChoices(), severities: severities },
|
|
1424
|
+
budgetInBytes: budget,
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
expect(getChoices(card, "incidentSeverity")).toEqual(severities.choices);
|
|
1428
|
+
|
|
1429
|
+
if (budget > 0) {
|
|
1430
|
+
expectWithinBudget(card, budget);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
describe("MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget, huge project (#4111)", () => {
|
|
1437
|
+
test.each(NON_ZERO_BUDGETS)(
|
|
1438
|
+
"fits %i bytes with the lists as the fetchers read them, and says what it left off",
|
|
1439
|
+
async (budget: number) => {
|
|
1440
|
+
serveHugeProject();
|
|
1441
|
+
|
|
1442
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1443
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
1444
|
+
PROJECT_ID,
|
|
1445
|
+
);
|
|
1446
|
+
|
|
1447
|
+
const card: JSONObject =
|
|
1448
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1449
|
+
{
|
|
1450
|
+
choices: choices,
|
|
1451
|
+
budgetInBytes: budget,
|
|
1452
|
+
initialTitle: "Database upgrade",
|
|
1453
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1454
|
+
timezone: "America/New_York",
|
|
1455
|
+
},
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
expectHugeMaintenanceCard({
|
|
1459
|
+
card: card,
|
|
1460
|
+
choices: choices,
|
|
1461
|
+
budgetInBytes: budget,
|
|
1462
|
+
timezoneNote: knownTimezoneNote("America/New_York"),
|
|
1463
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1464
|
+
});
|
|
1465
|
+
expect(getSubmitData(card)).toEqual({
|
|
1466
|
+
action: "SubmitNewScheduledMaintenance",
|
|
1467
|
+
timezone: "America/New_York",
|
|
1468
|
+
});
|
|
1469
|
+
expect(getInputOrFail(card, "scheduledMaintenanceTitle")["value"]).toBe(
|
|
1470
|
+
"Database upgrade",
|
|
1471
|
+
);
|
|
1472
|
+
},
|
|
1473
|
+
);
|
|
1474
|
+
|
|
1475
|
+
test.each(NON_ZERO_BUDGETS)(
|
|
1476
|
+
"fits %i bytes even when handed every monitor and label of the project",
|
|
1477
|
+
(budget: number) => {
|
|
1478
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1479
|
+
wholeHugeMaintenanceChoices();
|
|
1480
|
+
|
|
1481
|
+
const card: JSONObject =
|
|
1482
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1483
|
+
{
|
|
1484
|
+
choices: choices,
|
|
1485
|
+
budgetInBytes: budget,
|
|
1486
|
+
initialTitle: "t".repeat(MAINTENANCE_TITLE_MAX_LENGTH),
|
|
1487
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1488
|
+
},
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1491
|
+
expectHugeMaintenanceCard({
|
|
1492
|
+
card: card,
|
|
1493
|
+
choices: choices,
|
|
1494
|
+
budgetInBytes: budget,
|
|
1495
|
+
timezoneNote: GENERIC_TIMEZONE_NOTE,
|
|
1496
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1497
|
+
});
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
test.each([null, undefined, ""])(
|
|
1502
|
+
"offers no Create in OneUptime link without a URL (%p), at any budget",
|
|
1503
|
+
async (createInOneUptimeUrl: string | null | undefined) => {
|
|
1504
|
+
serveHugeProject();
|
|
1505
|
+
|
|
1506
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1507
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
1508
|
+
PROJECT_ID,
|
|
1509
|
+
);
|
|
1510
|
+
|
|
1511
|
+
for (const budget of NON_ZERO_BUDGETS) {
|
|
1512
|
+
expectHugeMaintenanceCard({
|
|
1513
|
+
card: MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1514
|
+
{
|
|
1515
|
+
choices: choices,
|
|
1516
|
+
budgetInBytes: budget,
|
|
1517
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
1518
|
+
},
|
|
1519
|
+
),
|
|
1520
|
+
choices: choices,
|
|
1521
|
+
budgetInBytes: budget,
|
|
1522
|
+
timezoneNote: GENERIC_TIMEZONE_NOTE,
|
|
1523
|
+
createInOneUptimeUrl: null,
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const budgetZeroCard: JSONObject =
|
|
1528
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1529
|
+
{
|
|
1530
|
+
choices: choices,
|
|
1531
|
+
budgetInBytes: 0,
|
|
1532
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
1533
|
+
},
|
|
1534
|
+
);
|
|
1535
|
+
|
|
1536
|
+
// The time zone note, then one note per list left off.
|
|
1537
|
+
expect(getNotes(budgetZeroCard)).toHaveLength(3);
|
|
1538
|
+
expectCreateInOneUptimeLink(budgetZeroCard, null);
|
|
1539
|
+
},
|
|
1540
|
+
);
|
|
1541
|
+
|
|
1542
|
+
test("a smaller budget shows no more of any list and makes a smaller card", () => {
|
|
1543
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1544
|
+
wholeHugeMaintenanceChoices();
|
|
1545
|
+
const trimmableIds: Array<string> = [
|
|
1546
|
+
"scheduledMaintenanceMonitors",
|
|
1547
|
+
"labels",
|
|
1548
|
+
];
|
|
1549
|
+
|
|
1550
|
+
const cards: Array<JSONObject> =
|
|
1551
|
+
MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES.map((budget: number) => {
|
|
1552
|
+
return MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1553
|
+
{
|
|
1554
|
+
choices: choices,
|
|
1555
|
+
budgetInBytes: budget,
|
|
1556
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1557
|
+
},
|
|
1558
|
+
);
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
for (let index: number = 1; index < cards.length; index++) {
|
|
1562
|
+
const larger: JSONObject = cards[index - 1]!;
|
|
1563
|
+
const smaller: JSONObject = cards[index]!;
|
|
1564
|
+
|
|
1565
|
+
expect(sizeOf(smaller)).toBeLessThan(sizeOf(larger));
|
|
1566
|
+
|
|
1567
|
+
const largerCounts: Array<number> = shownCounts(larger, trimmableIds);
|
|
1568
|
+
const smallerCounts: Array<number> = shownCounts(smaller, trimmableIds);
|
|
1569
|
+
|
|
1570
|
+
smallerCounts.forEach((count: number, listIndex: number) => {
|
|
1571
|
+
expect(count).toBeLessThanOrEqual(largerCounts[listIndex]!);
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
test("leaves the choices it was given alone, so every budget starts from the same lists", () => {
|
|
1577
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1578
|
+
wholeHugeMaintenanceChoices();
|
|
1579
|
+
const before: string = JSON.stringify(choices);
|
|
1580
|
+
|
|
1581
|
+
const build: (budget: number) => JSONObject = (
|
|
1582
|
+
budget: number,
|
|
1583
|
+
): JSONObject => {
|
|
1584
|
+
return MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1585
|
+
{
|
|
1586
|
+
choices: choices,
|
|
1587
|
+
budgetInBytes: budget,
|
|
1588
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1589
|
+
timezone: "Asia/Kolkata",
|
|
1590
|
+
},
|
|
1591
|
+
);
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
const first: JSONObject = build(FIRST_BUDGET);
|
|
1595
|
+
|
|
1596
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
1597
|
+
build(budget);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
expect(JSON.stringify(choices)).toBe(before);
|
|
1601
|
+
expect(build(FIRST_BUDGET)).toEqual(first);
|
|
1602
|
+
});
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
describe("a list the fetchers read only in part", () => {
|
|
1606
|
+
/*
|
|
1607
|
+
* 300 monitors with short names: the 250 the fetcher reads fit the first
|
|
1608
|
+
* budget whole, yet the card still does not offer every monitor, so it
|
|
1609
|
+
* says so and offers the dashboard.
|
|
1610
|
+
*/
|
|
1611
|
+
const MANY_SHORT_MONITOR_ROWS: Array<NamedRow> = buildRows({
|
|
1612
|
+
table: 10,
|
|
1613
|
+
count: 300,
|
|
1614
|
+
nameOf: (index: number): string => {
|
|
1615
|
+
return `m${index}`;
|
|
1616
|
+
},
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
test("incident form: shows every monitor read and counts the project's", async () => {
|
|
1620
|
+
serveProject({
|
|
1621
|
+
monitors: MANY_SHORT_MONITOR_ROWS,
|
|
1622
|
+
labels: SMALL_LABEL_ROWS,
|
|
1623
|
+
onCallDutyPolicies: SMALL_POLICY_ROWS,
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1627
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(PROJECT_ID);
|
|
1628
|
+
|
|
1629
|
+
const card: JSONObject =
|
|
1630
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1631
|
+
choices: choices,
|
|
1632
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1633
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
expectWithinBudget(card, FIRST_BUDGET);
|
|
1637
|
+
expect(getChoices(card, "incidentMonitors")).toHaveLength(
|
|
1638
|
+
MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
1639
|
+
);
|
|
1640
|
+
expect(getNotes(card)).toEqual([
|
|
1641
|
+
showingNote({
|
|
1642
|
+
shown: MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
1643
|
+
total: 300,
|
|
1644
|
+
noun: "monitors",
|
|
1645
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
1646
|
+
}),
|
|
1647
|
+
]);
|
|
1648
|
+
expect(getOpenUrlActions(card)).toEqual([
|
|
1649
|
+
{
|
|
1650
|
+
type: "Action.OpenUrl",
|
|
1651
|
+
title: "Create in OneUptime",
|
|
1652
|
+
url: CREATE_INCIDENT_URL,
|
|
1653
|
+
},
|
|
1654
|
+
]);
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
test("maintenance form: shows every monitor read and counts the project's", async () => {
|
|
1658
|
+
serveProject({
|
|
1659
|
+
monitors: MANY_SHORT_MONITOR_ROWS,
|
|
1660
|
+
labels: SMALL_LABEL_ROWS,
|
|
1661
|
+
onCallDutyPolicies: SMALL_POLICY_ROWS,
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
1665
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
1666
|
+
PROJECT_ID,
|
|
1667
|
+
);
|
|
1668
|
+
|
|
1669
|
+
const card: JSONObject =
|
|
1670
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1671
|
+
{
|
|
1672
|
+
choices: choices,
|
|
1673
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1674
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1675
|
+
},
|
|
1676
|
+
);
|
|
1677
|
+
|
|
1678
|
+
expectWithinBudget(card, FIRST_BUDGET);
|
|
1679
|
+
expect(getChoices(card, "scheduledMaintenanceMonitors")).toHaveLength(
|
|
1680
|
+
MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
1681
|
+
);
|
|
1682
|
+
expect(getNotes(card)).toEqual([
|
|
1683
|
+
GENERIC_TIMEZONE_NOTE,
|
|
1684
|
+
showingNote({
|
|
1685
|
+
shown: MICROSOFT_TEAMS_MAX_MONITOR_CHOICES,
|
|
1686
|
+
total: 300,
|
|
1687
|
+
noun: "monitors",
|
|
1688
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
1689
|
+
}),
|
|
1690
|
+
]);
|
|
1691
|
+
expect(getOpenUrlActions(card)).toEqual([
|
|
1692
|
+
{
|
|
1693
|
+
type: "Action.OpenUrl",
|
|
1694
|
+
title: "Create in OneUptime",
|
|
1695
|
+
url: CREATE_MAINTENANCE_URL,
|
|
1696
|
+
},
|
|
1697
|
+
]);
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
describe("budget 0: the form without its optional lists", () => {
|
|
1702
|
+
test("incident form keeps title, description and severity and names every list it left off", () => {
|
|
1703
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
1704
|
+
wholeHugeIncidentChoices();
|
|
1705
|
+
|
|
1706
|
+
const card: JSONObject =
|
|
1707
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1708
|
+
choices: choices,
|
|
1709
|
+
budgetInBytes: 0,
|
|
1710
|
+
initialTitle: "Checkout is down",
|
|
1711
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
// Each list's note where the list would have been.
|
|
1715
|
+
expect(layoutOf(card)).toEqual([
|
|
1716
|
+
"heading: Create New Incident",
|
|
1717
|
+
"Input.Text incidentTitle",
|
|
1718
|
+
"Input.Text incidentDescription",
|
|
1719
|
+
"Input.ChoiceSet incidentSeverity",
|
|
1720
|
+
`note: ${tooManyNote({
|
|
1721
|
+
total: HUGE_MONITOR_COUNT,
|
|
1722
|
+
noun: "monitors",
|
|
1723
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
1724
|
+
})}`,
|
|
1725
|
+
`note: ${tooManyNote({
|
|
1726
|
+
total: HUGE_POLICY_COUNT,
|
|
1727
|
+
noun: "on-call policies",
|
|
1728
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
1729
|
+
})}`,
|
|
1730
|
+
`note: ${tooManyNote({
|
|
1731
|
+
total: HUGE_LABEL_COUNT,
|
|
1732
|
+
noun: "labels",
|
|
1733
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
1734
|
+
})}`,
|
|
1735
|
+
]);
|
|
1736
|
+
expect(getInputOrFail(card, "incidentTitle")["value"]).toBe(
|
|
1737
|
+
"Checkout is down",
|
|
1738
|
+
);
|
|
1739
|
+
expect(getChoices(card, "incidentSeverity")).toEqual(
|
|
1740
|
+
choices.severities.choices,
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
expect(getSubmitData(card)).toEqual({ action: "SubmitNewIncident" });
|
|
1744
|
+
expectCreateInOneUptimeLink(card, CREATE_INCIDENT_URL);
|
|
1745
|
+
|
|
1746
|
+
// A handful of lines, whatever the size of the project.
|
|
1747
|
+
expect(sizeOf(card)).toBeLessThan(8 * 1024);
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
test("maintenance form keeps title, description, dates and time zone and names every list it left off", () => {
|
|
1751
|
+
const card: JSONObject =
|
|
1752
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1753
|
+
{
|
|
1754
|
+
choices: wholeHugeMaintenanceChoices(),
|
|
1755
|
+
budgetInBytes: 0,
|
|
1756
|
+
initialTitle: "Database upgrade",
|
|
1757
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1758
|
+
timezone: "Australia/Adelaide",
|
|
1759
|
+
},
|
|
1760
|
+
);
|
|
1761
|
+
|
|
1762
|
+
expect(layoutOf(card)).toEqual([
|
|
1763
|
+
"heading: Create New Scheduled Maintenance",
|
|
1764
|
+
"Input.Text scheduledMaintenanceTitle",
|
|
1765
|
+
"Input.Text scheduledMaintenanceDescription",
|
|
1766
|
+
`note: ${knownTimezoneNote("Australia/Adelaide")}`,
|
|
1767
|
+
"Input.Date startDate",
|
|
1768
|
+
"Input.Time startTime",
|
|
1769
|
+
"Input.Date endDate",
|
|
1770
|
+
"Input.Time endTime",
|
|
1771
|
+
`note: ${tooManyNote({
|
|
1772
|
+
total: HUGE_MONITOR_COUNT,
|
|
1773
|
+
noun: "monitors",
|
|
1774
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
1775
|
+
})}`,
|
|
1776
|
+
`note: ${tooManyNote({
|
|
1777
|
+
total: HUGE_LABEL_COUNT,
|
|
1778
|
+
noun: "labels",
|
|
1779
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
1780
|
+
})}`,
|
|
1781
|
+
]);
|
|
1782
|
+
expect(getInputOrFail(card, "scheduledMaintenanceTitle")["value"]).toBe(
|
|
1783
|
+
"Database upgrade",
|
|
1784
|
+
);
|
|
1785
|
+
|
|
1786
|
+
expect(getSubmitData(card)).toEqual({
|
|
1787
|
+
action: "SubmitNewScheduledMaintenance",
|
|
1788
|
+
timezone: "Australia/Adelaide",
|
|
1789
|
+
});
|
|
1790
|
+
expectCreateInOneUptimeLink(card, CREATE_MAINTENANCE_URL);
|
|
1791
|
+
expect(sizeOf(card)).toBeLessThan(8 * 1024);
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
test("a list the project does not have gets no note", () => {
|
|
1795
|
+
const incidentCard: JSONObject =
|
|
1796
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1797
|
+
choices: {
|
|
1798
|
+
...wholeHugeIncidentChoices(),
|
|
1799
|
+
labels: emptyList(),
|
|
1800
|
+
onCallDutyPolicies: emptyList(),
|
|
1801
|
+
},
|
|
1802
|
+
budgetInBytes: 0,
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
expect(getNotes(incidentCard)).toEqual([
|
|
1806
|
+
tooManyNote({
|
|
1807
|
+
total: HUGE_MONITOR_COUNT,
|
|
1808
|
+
noun: "monitors",
|
|
1809
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
1810
|
+
}),
|
|
1811
|
+
]);
|
|
1812
|
+
|
|
1813
|
+
const maintenanceCard: JSONObject =
|
|
1814
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1815
|
+
{
|
|
1816
|
+
choices: {
|
|
1817
|
+
...wholeHugeMaintenanceChoices(),
|
|
1818
|
+
monitors: emptyList(),
|
|
1819
|
+
},
|
|
1820
|
+
budgetInBytes: 0,
|
|
1821
|
+
},
|
|
1822
|
+
);
|
|
1823
|
+
|
|
1824
|
+
expect(getNotes(maintenanceCard)).toEqual([
|
|
1825
|
+
GENERIC_TIMEZONE_NOTE,
|
|
1826
|
+
tooManyNote({
|
|
1827
|
+
total: HUGE_LABEL_COUNT,
|
|
1828
|
+
noun: "labels",
|
|
1829
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
1830
|
+
}),
|
|
1831
|
+
]);
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
/*
|
|
1835
|
+
* Budget 0 is the last try, after Teams refused the card twice. The lists
|
|
1836
|
+
* are left off up front, so each form is built, and measured, once: the
|
|
1837
|
+
* card with every choice, the biggest of all, is never built, and no list
|
|
1838
|
+
* is shortened step by step to nothing first.
|
|
1839
|
+
*/
|
|
1840
|
+
test("each form is built and measured once, however many choices the project has", () => {
|
|
1841
|
+
const sizeSpy: SpyInstance<
|
|
1842
|
+
typeof MicrosoftTeamsMessageSize.getSizeInBytes
|
|
1843
|
+
> = jest.spyOn(MicrosoftTeamsMessageSize, "getSizeInBytes");
|
|
1844
|
+
|
|
1845
|
+
const incidentCard: JSONObject =
|
|
1846
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1847
|
+
choices: wholeHugeIncidentChoices(),
|
|
1848
|
+
budgetInBytes: 0,
|
|
1849
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
1850
|
+
});
|
|
1851
|
+
const maintenanceCard: JSONObject =
|
|
1852
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1853
|
+
{
|
|
1854
|
+
choices: wholeHugeMaintenanceChoices(),
|
|
1855
|
+
budgetInBytes: 0,
|
|
1856
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
1857
|
+
timezone: "Europe/Berlin",
|
|
1858
|
+
},
|
|
1859
|
+
);
|
|
1860
|
+
|
|
1861
|
+
const measured: Array<string | JSONObject> = sizeSpy.mock.calls.map(
|
|
1862
|
+
(call: [value: string | JSONObject]): string | JSONObject => {
|
|
1863
|
+
return call[0];
|
|
1864
|
+
},
|
|
1865
|
+
);
|
|
1866
|
+
|
|
1867
|
+
expect(measured).toHaveLength(2);
|
|
1868
|
+
expect(measured[0]).toBe(incidentCard);
|
|
1869
|
+
expect(measured[1]).toBe(maintenanceCard);
|
|
1870
|
+
});
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
describe("the monitor status list goes with the monitor list", () => {
|
|
1874
|
+
test("is left off with the monitors at budget 0", () => {
|
|
1875
|
+
const incidentCard: JSONObject =
|
|
1876
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1877
|
+
choices: smallIncidentChoices(),
|
|
1878
|
+
budgetInBytes: 0,
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
expect(getInput(incidentCard, "incidentMonitors")).toBeUndefined();
|
|
1882
|
+
expect(getInput(incidentCard, "monitorStatus")).toBeUndefined();
|
|
1883
|
+
|
|
1884
|
+
const maintenanceCard: JSONObject =
|
|
1885
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1886
|
+
{
|
|
1887
|
+
choices: smallMaintenanceChoices(),
|
|
1888
|
+
budgetInBytes: 0,
|
|
1889
|
+
},
|
|
1890
|
+
);
|
|
1891
|
+
|
|
1892
|
+
expect(
|
|
1893
|
+
getInput(maintenanceCard, "scheduledMaintenanceMonitors"),
|
|
1894
|
+
).toBeUndefined();
|
|
1895
|
+
expect(getInput(maintenanceCard, "monitorStatus")).toBeUndefined();
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
test("is not offered to a project without monitors", () => {
|
|
1899
|
+
const incidentCard: JSONObject =
|
|
1900
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1901
|
+
choices: { ...smallIncidentChoices(), monitors: emptyList() },
|
|
1902
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1903
|
+
});
|
|
1904
|
+
|
|
1905
|
+
expect(getInput(incidentCard, "monitorStatus")).toBeUndefined();
|
|
1906
|
+
// Nothing was left off, so nothing is said about it.
|
|
1907
|
+
expect(getNotes(incidentCard)).toEqual([]);
|
|
1908
|
+
|
|
1909
|
+
const maintenanceCard: JSONObject =
|
|
1910
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1911
|
+
{
|
|
1912
|
+
choices: { ...smallMaintenanceChoices(), monitors: emptyList() },
|
|
1913
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1914
|
+
},
|
|
1915
|
+
);
|
|
1916
|
+
|
|
1917
|
+
expect(getInput(maintenanceCard, "monitorStatus")).toBeUndefined();
|
|
1918
|
+
expect(getNotes(maintenanceCard)).toEqual([GENERIC_TIMEZONE_NOTE]);
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
test("is not an empty dropdown when the project has no monitor statuses", () => {
|
|
1922
|
+
const incidentCard: JSONObject =
|
|
1923
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1924
|
+
choices: { ...smallIncidentChoices(), monitorStatuses: emptyList() },
|
|
1925
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
expect(getChoices(incidentCard, "incidentMonitors")).toHaveLength(3);
|
|
1929
|
+
expect(getInput(incidentCard, "monitorStatus")).toBeUndefined();
|
|
1930
|
+
|
|
1931
|
+
const maintenanceCard: JSONObject =
|
|
1932
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
1933
|
+
{
|
|
1934
|
+
choices: {
|
|
1935
|
+
...smallMaintenanceChoices(),
|
|
1936
|
+
monitorStatuses: emptyList(),
|
|
1937
|
+
},
|
|
1938
|
+
budgetInBytes: FIRST_BUDGET,
|
|
1939
|
+
},
|
|
1940
|
+
);
|
|
1941
|
+
|
|
1942
|
+
expect(
|
|
1943
|
+
getChoices(maintenanceCard, "scheduledMaintenanceMonitors"),
|
|
1944
|
+
).toHaveLength(3);
|
|
1945
|
+
expect(getInput(maintenanceCard, "monitorStatus")).toBeUndefined();
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1948
|
+
/*
|
|
1949
|
+
* Only the monitor, label and on-call policy lists are shortened to fit,
|
|
1950
|
+
* and only they get a note saying so. With as many long status names as
|
|
1951
|
+
* the fetcher reads, the statuses are the longest list on the card once
|
|
1952
|
+
* the others are shortened: a status list shortened with them would drop
|
|
1953
|
+
* statuses without a word.
|
|
1954
|
+
*/
|
|
1955
|
+
const LONG_STATUSES: MicrosoftTeamsCardChoiceList = listOf(
|
|
1956
|
+
buildRows({
|
|
1957
|
+
table: 14,
|
|
1958
|
+
count: MICROSOFT_TEAMS_MAX_MONITOR_STATUS_CHOICES,
|
|
1959
|
+
nameOf: (index: number): string => {
|
|
1960
|
+
return `Status ${padded(index)} ${"s".repeat(80)}`.substring(0, 80);
|
|
1961
|
+
},
|
|
1962
|
+
}),
|
|
1963
|
+
);
|
|
1964
|
+
|
|
1965
|
+
test("is never shortened on the incident form, even when it is what makes the card big", () => {
|
|
1966
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices = {
|
|
1967
|
+
...wholeHugeIncidentChoices(),
|
|
1968
|
+
monitorStatuses: LONG_STATUSES,
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
for (const budget of NON_ZERO_BUDGETS) {
|
|
1972
|
+
const card: JSONObject =
|
|
1973
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
1974
|
+
choices: choices,
|
|
1975
|
+
budgetInBytes: budget,
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
expectWithinBudget(card, budget);
|
|
1979
|
+
|
|
1980
|
+
// Monitors are offered at the first budget at least.
|
|
1981
|
+
if (budget === FIRST_BUDGET) {
|
|
1982
|
+
expect(getChoices(card, "incidentMonitors").length).toBeGreaterThan(0);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
if (getChoices(card, "incidentMonitors").length > 0) {
|
|
1986
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
1987
|
+
LONG_STATUSES.choices,
|
|
1988
|
+
);
|
|
1989
|
+
} else {
|
|
1990
|
+
expect(getInput(card, "monitorStatus")).toBeUndefined();
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// A note for each shortened list, none of them about statuses.
|
|
1994
|
+
expect(getNotes(card)).toHaveLength(3);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
test("is never shortened on the maintenance form, even when it is what makes the card big", () => {
|
|
1999
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices = {
|
|
2000
|
+
...wholeHugeMaintenanceChoices(),
|
|
2001
|
+
monitorStatuses: LONG_STATUSES,
|
|
2002
|
+
};
|
|
2003
|
+
|
|
2004
|
+
for (const budget of NON_ZERO_BUDGETS) {
|
|
2005
|
+
const card: JSONObject =
|
|
2006
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2007
|
+
{
|
|
2008
|
+
choices: choices,
|
|
2009
|
+
budgetInBytes: budget,
|
|
2010
|
+
},
|
|
2011
|
+
);
|
|
2012
|
+
|
|
2013
|
+
expectWithinBudget(card, budget);
|
|
2014
|
+
|
|
2015
|
+
if (budget === FIRST_BUDGET) {
|
|
2016
|
+
expect(
|
|
2017
|
+
getChoices(card, "scheduledMaintenanceMonitors").length,
|
|
2018
|
+
).toBeGreaterThan(0);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
if (getChoices(card, "scheduledMaintenanceMonitors").length > 0) {
|
|
2022
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
2023
|
+
LONG_STATUSES.choices,
|
|
2024
|
+
);
|
|
2025
|
+
} else {
|
|
2026
|
+
expect(getInput(card, "monitorStatus")).toBeUndefined();
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// The time zone note, then one note per shortened list.
|
|
2030
|
+
expect(getNotes(card)).toHaveLength(3);
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
});
|
|
2034
|
+
|
|
2035
|
+
describe("a project with none of a list", () => {
|
|
2036
|
+
/*
|
|
2037
|
+
* A dropdown with no choices is no use, and a required one (the severity)
|
|
2038
|
+
* could never be submitted. The command answers a project without
|
|
2039
|
+
* severities itself, before any card is built.
|
|
2040
|
+
*/
|
|
2041
|
+
test("gets no empty dropdown, no note and no link, on either form", () => {
|
|
2042
|
+
const incidentCard: JSONObject =
|
|
2043
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
2044
|
+
choices: {
|
|
2045
|
+
severities: emptyList(),
|
|
2046
|
+
monitors: emptyList(),
|
|
2047
|
+
monitorStatuses: emptyList(),
|
|
2048
|
+
labels: emptyList(),
|
|
2049
|
+
onCallDutyPolicies: emptyList(),
|
|
2050
|
+
},
|
|
2051
|
+
budgetInBytes: FIRST_BUDGET,
|
|
2052
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
2053
|
+
});
|
|
2054
|
+
|
|
2055
|
+
expect(describeInputs(incidentCard)).toEqual([
|
|
2056
|
+
"Input.Text incidentTitle",
|
|
2057
|
+
"Input.Text incidentDescription",
|
|
2058
|
+
]);
|
|
2059
|
+
expect(getNotes(incidentCard)).toEqual([]);
|
|
2060
|
+
expect(getActionTypes(incidentCard)).toEqual(["Action.Submit"]);
|
|
2061
|
+
|
|
2062
|
+
const maintenanceCard: JSONObject =
|
|
2063
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2064
|
+
{
|
|
2065
|
+
choices: {
|
|
2066
|
+
monitors: emptyList(),
|
|
2067
|
+
monitorStatuses: emptyList(),
|
|
2068
|
+
labels: emptyList(),
|
|
2069
|
+
},
|
|
2070
|
+
budgetInBytes: FIRST_BUDGET,
|
|
2071
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
2072
|
+
},
|
|
2073
|
+
);
|
|
2074
|
+
|
|
2075
|
+
expect(describeInputs(maintenanceCard)).toEqual([
|
|
2076
|
+
"Input.Text scheduledMaintenanceTitle",
|
|
2077
|
+
"Input.Text scheduledMaintenanceDescription",
|
|
2078
|
+
"Input.Date startDate",
|
|
2079
|
+
"Input.Time startTime",
|
|
2080
|
+
"Input.Date endDate",
|
|
2081
|
+
"Input.Time endTime",
|
|
2082
|
+
]);
|
|
2083
|
+
expect(getNotes(maintenanceCard)).toEqual([GENERIC_TIMEZONE_NOTE]);
|
|
2084
|
+
expect(getActionTypes(maintenanceCard)).toEqual(["Action.Submit"]);
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
describe("every card the bot can send is a form Teams can show", () => {
|
|
2089
|
+
/*
|
|
2090
|
+
* Whatever the project and whichever budget the card was fitted to: every
|
|
2091
|
+
* input is labelled (Teams shows a required input's error under its label)
|
|
2092
|
+
* and has an id no other input has (the submitted values are keyed by id),
|
|
2093
|
+
* the required inputs stay required, every dropdown is the compact kind
|
|
2094
|
+
* the form always used and has choices to pick from, the small print
|
|
2095
|
+
* wraps instead of being cut at the end of its first line, the submit is
|
|
2096
|
+
* the same, and the link to the dashboard follows it when, and only when,
|
|
2097
|
+
* a note says a list was left off and there is a URL to offer.
|
|
2098
|
+
*/
|
|
2099
|
+
function expectUsableForm(data: {
|
|
2100
|
+
card: JSONObject;
|
|
2101
|
+
requiredIds: Array<string>;
|
|
2102
|
+
submitAction: string;
|
|
2103
|
+
// The URL the card was given for "Create in OneUptime", or null.
|
|
2104
|
+
createInOneUptimeUrl: string | null;
|
|
2105
|
+
}): void {
|
|
2106
|
+
const { card } = data;
|
|
2107
|
+
|
|
2108
|
+
expect(card).toMatchObject({
|
|
2109
|
+
type: "AdaptiveCard",
|
|
2110
|
+
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
2111
|
+
version: "1.5",
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
const inputs: Array<JSONObject> = bodyOf(card).filter(
|
|
2115
|
+
(element: JSONObject) => {
|
|
2116
|
+
return typeof element["id"] === "string";
|
|
2117
|
+
},
|
|
2118
|
+
);
|
|
2119
|
+
const ids: Array<string> = inputs.map((input: JSONObject) => {
|
|
2120
|
+
return input["id"] as string;
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
2124
|
+
|
|
2125
|
+
for (const input of inputs) {
|
|
2126
|
+
expect(String(input["type"]).startsWith("Input.")).toBe(true);
|
|
2127
|
+
expect(String(input["label"] || "").trim()).not.toBe("");
|
|
2128
|
+
|
|
2129
|
+
if (input["type"] === "Input.ChoiceSet") {
|
|
2130
|
+
const choices: Array<MicrosoftTeamsCardChoice> = input[
|
|
2131
|
+
"choices"
|
|
2132
|
+
] as Array<MicrosoftTeamsCardChoice>;
|
|
2133
|
+
|
|
2134
|
+
expect(input["style"]).toBe("compact");
|
|
2135
|
+
expect(choices.length).toBeGreaterThan(0);
|
|
2136
|
+
|
|
2137
|
+
for (const choice of choices) {
|
|
2138
|
+
expect(choice.title.trim()).not.toBe("");
|
|
2139
|
+
expect(choice.value.trim()).not.toBe("");
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
for (const requiredId of data.requiredIds) {
|
|
2145
|
+
expect(getInputOrFail(card, requiredId)["isRequired"]).toBe(true);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
for (const element of bodyOf(card)) {
|
|
2149
|
+
if (element["type"] === "TextBlock" && element["isSubtle"] === true) {
|
|
2150
|
+
expect(element["wrap"]).toBe(true);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
expect(getSubmitData(card)["action"]).toBe(data.submitAction);
|
|
2155
|
+
|
|
2156
|
+
const isAnythingLeftOff: boolean = getNotes(card).some((note: string) => {
|
|
2157
|
+
return (
|
|
2158
|
+
note.startsWith("Showing the first ") ||
|
|
2159
|
+
note.startsWith("This project has ")
|
|
2160
|
+
);
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
expectCreateInOneUptimeLink(
|
|
2164
|
+
card,
|
|
2165
|
+
isAnythingLeftOff ? data.createInOneUptimeUrl : null,
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
test("the incident form", () => {
|
|
2170
|
+
const projects: Array<MicrosoftTeamsNewIncidentFormChoices> = [
|
|
2171
|
+
smallIncidentChoices(),
|
|
2172
|
+
wholeHugeIncidentChoices(),
|
|
2173
|
+
{
|
|
2174
|
+
severities: listOf(SEVERITY_ROWS),
|
|
2175
|
+
monitors: emptyList(),
|
|
2176
|
+
monitorStatuses: listOf(MONITOR_STATUS_ROWS),
|
|
2177
|
+
labels: emptyList(),
|
|
2178
|
+
onCallDutyPolicies: emptyList(),
|
|
2179
|
+
},
|
|
2180
|
+
];
|
|
2181
|
+
|
|
2182
|
+
for (const choices of projects) {
|
|
2183
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2184
|
+
for (const createInOneUptimeUrl of [CREATE_INCIDENT_URL, null]) {
|
|
2185
|
+
expectUsableForm({
|
|
2186
|
+
card: MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
2187
|
+
choices: choices,
|
|
2188
|
+
budgetInBytes: budget,
|
|
2189
|
+
initialTitle: "Checkout is down",
|
|
2190
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
2191
|
+
}),
|
|
2192
|
+
requiredIds: [
|
|
2193
|
+
"incidentTitle",
|
|
2194
|
+
"incidentDescription",
|
|
2195
|
+
"incidentSeverity",
|
|
2196
|
+
],
|
|
2197
|
+
submitAction: MicrosoftTeamsIncidentActionType.SubmitNewIncident,
|
|
2198
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2205
|
+
test("the maintenance form", () => {
|
|
2206
|
+
const projects: Array<MicrosoftTeamsNewScheduledMaintenanceFormChoices> = [
|
|
2207
|
+
smallMaintenanceChoices(),
|
|
2208
|
+
wholeHugeMaintenanceChoices(),
|
|
2209
|
+
{
|
|
2210
|
+
monitors: emptyList(),
|
|
2211
|
+
monitorStatuses: listOf(MONITOR_STATUS_ROWS),
|
|
2212
|
+
labels: emptyList(),
|
|
2213
|
+
},
|
|
2214
|
+
];
|
|
2215
|
+
|
|
2216
|
+
for (const choices of projects) {
|
|
2217
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2218
|
+
for (const createInOneUptimeUrl of [CREATE_MAINTENANCE_URL, null]) {
|
|
2219
|
+
expectUsableForm({
|
|
2220
|
+
card: MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2221
|
+
{
|
|
2222
|
+
choices: choices,
|
|
2223
|
+
budgetInBytes: budget,
|
|
2224
|
+
initialTitle: "Database upgrade",
|
|
2225
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
2226
|
+
timezone: "Europe/Berlin",
|
|
2227
|
+
},
|
|
2228
|
+
),
|
|
2229
|
+
requiredIds: [
|
|
2230
|
+
"scheduledMaintenanceTitle",
|
|
2231
|
+
"scheduledMaintenanceDescription",
|
|
2232
|
+
"startDate",
|
|
2233
|
+
"startTime",
|
|
2234
|
+
"endDate",
|
|
2235
|
+
"endTime",
|
|
2236
|
+
],
|
|
2237
|
+
submitAction:
|
|
2238
|
+
MicrosoftTeamsScheduledMaintenanceActionType.SubmitNewScheduledMaintenance,
|
|
2239
|
+
createInOneUptimeUrl: createInOneUptimeUrl,
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
describe("the notes and the dashboard button are the ones MicrosoftTeamsCardChoices builds", () => {
|
|
2248
|
+
/*
|
|
2249
|
+
* Both forms build their small print and their "Create in OneUptime"
|
|
2250
|
+
* button with the shared MicrosoftTeamsCardChoices helpers (each form used
|
|
2251
|
+
* to have a copy of the note element), so the two forms look the same. The
|
|
2252
|
+
* note under each list is the one buildNotShownNoteElement makes for that
|
|
2253
|
+
* list as the card shows it.
|
|
2254
|
+
*/
|
|
2255
|
+
type NotedList = {
|
|
2256
|
+
inputId: string;
|
|
2257
|
+
list: MicrosoftTeamsCardChoiceList;
|
|
2258
|
+
noun: string;
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2261
|
+
function expectSharedElements(data: {
|
|
2262
|
+
card: JSONObject;
|
|
2263
|
+
// Small print that is not about a list, in card order, above the lists.
|
|
2264
|
+
otherNotes: Array<string>;
|
|
2265
|
+
// The lists that get a note, in card order.
|
|
2266
|
+
lists: Array<NotedList>;
|
|
2267
|
+
hint: string;
|
|
2268
|
+
createInOneUptimeUrl: string;
|
|
2269
|
+
}): void {
|
|
2270
|
+
const listNotes: Array<JSONObject> = [];
|
|
2271
|
+
|
|
2272
|
+
for (const noted of data.lists) {
|
|
2273
|
+
const note: JSONObject | null =
|
|
2274
|
+
MicrosoftTeamsCardChoices.buildNotShownNoteElement({
|
|
2275
|
+
list: {
|
|
2276
|
+
choices: getChoices(data.card, noted.inputId),
|
|
2277
|
+
totalCount: noted.list.totalCount,
|
|
2278
|
+
},
|
|
2279
|
+
pluralNoun: noted.noun,
|
|
2280
|
+
addLaterHint: data.hint,
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
// Every list of this project is shortened or left off, so has a note.
|
|
2284
|
+
expect(note).not.toBeNull();
|
|
2285
|
+
listNotes.push(note!);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
const smallPrint: Array<JSONObject> = bodyOf(data.card).filter(
|
|
2289
|
+
(element: JSONObject) => {
|
|
2290
|
+
return element["type"] === "TextBlock" && element["isSubtle"] === true;
|
|
2291
|
+
},
|
|
2292
|
+
);
|
|
2293
|
+
|
|
2294
|
+
expect(smallPrint).toStrictEqual([
|
|
2295
|
+
...data.otherNotes.map((text: string): JSONObject => {
|
|
2296
|
+
return MicrosoftTeamsCardChoices.buildNoteElement(text);
|
|
2297
|
+
}),
|
|
2298
|
+
...listNotes,
|
|
2299
|
+
]);
|
|
2300
|
+
expect(getOpenUrlActions(data.card)).toStrictEqual([
|
|
2301
|
+
MicrosoftTeamsCardChoices.buildCreateInOneUptimeAction(
|
|
2302
|
+
data.createInOneUptimeUrl,
|
|
2303
|
+
),
|
|
2304
|
+
]);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
test("the incident form, at every budget", () => {
|
|
2308
|
+
const choices: MicrosoftTeamsNewIncidentFormChoices =
|
|
2309
|
+
wholeHugeIncidentChoices();
|
|
2310
|
+
|
|
2311
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2312
|
+
expectSharedElements({
|
|
2313
|
+
card: MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
2314
|
+
choices: choices,
|
|
2315
|
+
budgetInBytes: budget,
|
|
2316
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
2317
|
+
}),
|
|
2318
|
+
otherNotes: [],
|
|
2319
|
+
lists: [
|
|
2320
|
+
{
|
|
2321
|
+
inputId: "incidentMonitors",
|
|
2322
|
+
list: choices.monitors,
|
|
2323
|
+
noun: "monitors",
|
|
2324
|
+
},
|
|
2325
|
+
{
|
|
2326
|
+
inputId: "onCallDutyPolicies",
|
|
2327
|
+
list: choices.onCallDutyPolicies,
|
|
2328
|
+
noun: "on-call policies",
|
|
2329
|
+
},
|
|
2330
|
+
{ inputId: "labels", list: choices.labels, noun: "labels" },
|
|
2331
|
+
],
|
|
2332
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
2333
|
+
createInOneUptimeUrl: CREATE_INCIDENT_URL,
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
|
|
2338
|
+
test("the maintenance form, time zone note included, at every budget", () => {
|
|
2339
|
+
const choices: MicrosoftTeamsNewScheduledMaintenanceFormChoices =
|
|
2340
|
+
wholeHugeMaintenanceChoices();
|
|
2341
|
+
|
|
2342
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2343
|
+
for (const timezone of ["Asia/Kolkata", undefined]) {
|
|
2344
|
+
expectSharedElements({
|
|
2345
|
+
card: MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2346
|
+
{
|
|
2347
|
+
choices: choices,
|
|
2348
|
+
budgetInBytes: budget,
|
|
2349
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
2350
|
+
timezone: timezone,
|
|
2351
|
+
},
|
|
2352
|
+
),
|
|
2353
|
+
otherNotes: [
|
|
2354
|
+
timezone ? knownTimezoneNote(timezone) : GENERIC_TIMEZONE_NOTE,
|
|
2355
|
+
],
|
|
2356
|
+
lists: [
|
|
2357
|
+
{
|
|
2358
|
+
inputId: "scheduledMaintenanceMonitors",
|
|
2359
|
+
list: choices.monitors,
|
|
2360
|
+
noun: "monitors",
|
|
2361
|
+
},
|
|
2362
|
+
{ inputId: "labels", list: choices.labels, noun: "labels" },
|
|
2363
|
+
],
|
|
2364
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
2365
|
+
createInOneUptimeUrl: CREATE_MAINTENANCE_URL,
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
});
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2372
|
+
describe("the title typed after the command", () => {
|
|
2373
|
+
type TitleForm = {
|
|
2374
|
+
name: string;
|
|
2375
|
+
titleInputId: string;
|
|
2376
|
+
maxLength: number;
|
|
2377
|
+
build: (initialTitle: string | undefined, budget: number) => JSONObject;
|
|
2378
|
+
};
|
|
2379
|
+
|
|
2380
|
+
const TITLE_FORMS: Array<TitleForm> = [
|
|
2381
|
+
{
|
|
2382
|
+
name: "incident",
|
|
2383
|
+
titleInputId: "incidentTitle",
|
|
2384
|
+
maxLength: INCIDENT_TITLE_MAX_LENGTH,
|
|
2385
|
+
build: (initialTitle: string | undefined, budget: number): JSONObject => {
|
|
2386
|
+
return MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
2387
|
+
choices: smallIncidentChoices(),
|
|
2388
|
+
budgetInBytes: budget,
|
|
2389
|
+
initialTitle: initialTitle,
|
|
2390
|
+
});
|
|
2391
|
+
},
|
|
2392
|
+
},
|
|
2393
|
+
{
|
|
2394
|
+
name: "maintenance",
|
|
2395
|
+
titleInputId: "scheduledMaintenanceTitle",
|
|
2396
|
+
maxLength: MAINTENANCE_TITLE_MAX_LENGTH,
|
|
2397
|
+
build: (initialTitle: string | undefined, budget: number): JSONObject => {
|
|
2398
|
+
return MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2399
|
+
{
|
|
2400
|
+
choices: smallMaintenanceChoices(),
|
|
2401
|
+
budgetInBytes: budget,
|
|
2402
|
+
initialTitle: initialTitle,
|
|
2403
|
+
},
|
|
2404
|
+
);
|
|
2405
|
+
},
|
|
2406
|
+
},
|
|
2407
|
+
];
|
|
2408
|
+
|
|
2409
|
+
function titleInput(
|
|
2410
|
+
form: TitleForm,
|
|
2411
|
+
initialTitle: string | undefined,
|
|
2412
|
+
budget?: number,
|
|
2413
|
+
): JSONObject {
|
|
2414
|
+
return getInputOrFail(
|
|
2415
|
+
form.build(initialTitle, budget ?? FIRST_BUDGET),
|
|
2416
|
+
form.titleInputId,
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
test.each(TITLE_FORMS)(
|
|
2421
|
+
"$name: is prefilled, trimmed, at every budget",
|
|
2422
|
+
(form: TitleForm) => {
|
|
2423
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2424
|
+
expect(
|
|
2425
|
+
titleInput(form, " Checkout is down \n", budget)["value"],
|
|
2426
|
+
).toBe("Checkout is down");
|
|
2427
|
+
}
|
|
2428
|
+
},
|
|
2429
|
+
);
|
|
2430
|
+
|
|
2431
|
+
test.each(TITLE_FORMS)(
|
|
2432
|
+
"$name: is cut to what the title column holds, keeping its start",
|
|
2433
|
+
(form: TitleForm) => {
|
|
2434
|
+
const longTitle: string =
|
|
2435
|
+
"a".repeat(form.maxLength) + "b".repeat(form.maxLength);
|
|
2436
|
+
|
|
2437
|
+
expect(titleInput(form, ` ${longTitle} `)["value"]).toBe(
|
|
2438
|
+
"a".repeat(form.maxLength),
|
|
2439
|
+
);
|
|
2440
|
+
expect(titleInput(form, "a".repeat(form.maxLength))["value"]).toBe(
|
|
2441
|
+
"a".repeat(form.maxLength),
|
|
2442
|
+
);
|
|
2443
|
+
},
|
|
2444
|
+
);
|
|
2445
|
+
|
|
2446
|
+
test.each(TITLE_FORMS)(
|
|
2447
|
+
"$name: leaves the title empty when nothing was typed after the command",
|
|
2448
|
+
(form: TitleForm) => {
|
|
2449
|
+
for (const initialTitle of [undefined, "", " ", "\n\t"]) {
|
|
2450
|
+
expect(titleInput(form, initialTitle)).not.toHaveProperty("value");
|
|
2451
|
+
}
|
|
2452
|
+
},
|
|
2453
|
+
);
|
|
2454
|
+
|
|
2455
|
+
test.each(TITLE_FORMS)(
|
|
2456
|
+
"$name: the title input is required and limited to what the column holds",
|
|
2457
|
+
(form: TitleForm) => {
|
|
2458
|
+
for (const initialTitle of [undefined, "Checkout is down"]) {
|
|
2459
|
+
expect(titleInput(form, initialTitle)).toMatchObject({
|
|
2460
|
+
type: "Input.Text",
|
|
2461
|
+
isRequired: true,
|
|
2462
|
+
maxLength: form.maxLength,
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
},
|
|
2466
|
+
);
|
|
2467
|
+
|
|
2468
|
+
/*
|
|
2469
|
+
* An emoji is two UTF-16 code units. A cut between them leaves half of it,
|
|
2470
|
+
* a lone surrogate: the card carries "\ud83d", Teams shows a broken
|
|
2471
|
+
* character at the end of the prefilled title, and a title submitted as it
|
|
2472
|
+
* is keeps it as U+FFFD (a lone surrogate cannot be encoded as UTF-8).
|
|
2473
|
+
* Both forms cut the title with truncateToLength
|
|
2474
|
+
* (Server/Utils/Database/TruncateColumnValue.ts), which drops such a half,
|
|
2475
|
+
* as MicrosoftTeamsMessageSize.fitTextToBudget does for replies.
|
|
2476
|
+
*/
|
|
2477
|
+
test.each(TITLE_FORMS)(
|
|
2478
|
+
"$name: is not cut through an emoji",
|
|
2479
|
+
(form: TitleForm) => {
|
|
2480
|
+
const title: string = `${"x".repeat(form.maxLength - 1)}🔧 maintenance window`;
|
|
2481
|
+
|
|
2482
|
+
const value: string = titleInput(form, title)["value"] as string;
|
|
2483
|
+
|
|
2484
|
+
expect(hasHalfAnEmoji(value)).toBe(false);
|
|
2485
|
+
expect(value.length).toBeLessThanOrEqual(form.maxLength);
|
|
2486
|
+
// The longest start of the title, in whole characters, that fits.
|
|
2487
|
+
expect(value).toBe("x".repeat(form.maxLength - 1));
|
|
2488
|
+
},
|
|
2489
|
+
);
|
|
2490
|
+
|
|
2491
|
+
test.each(TITLE_FORMS)(
|
|
2492
|
+
"$name: keeps a whole emoji that ends right at the cut",
|
|
2493
|
+
(form: TitleForm) => {
|
|
2494
|
+
// The emoji is the last two code units the title column holds.
|
|
2495
|
+
const title: string = `${"x".repeat(form.maxLength - 2)}🔧 maintenance window`;
|
|
2496
|
+
|
|
2497
|
+
const value: string = titleInput(form, title)["value"] as string;
|
|
2498
|
+
|
|
2499
|
+
// Only what is past the column goes: the emoji stays.
|
|
2500
|
+
expect(value).toBe(`${"x".repeat(form.maxLength - 2)}🔧`);
|
|
2501
|
+
expect(value).toHaveLength(form.maxLength);
|
|
2502
|
+
},
|
|
2503
|
+
);
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
describe("the maintenance form's time zone", () => {
|
|
2507
|
+
/*
|
|
2508
|
+
* The submit of someone who is somewhere else: Teams names their own zone
|
|
2509
|
+
* on the activity (localTimezone and the clientInfo entity), and their
|
|
2510
|
+
* local timestamp carries their own UTC offset.
|
|
2511
|
+
*/
|
|
2512
|
+
const SUBMITTED_FROM_AUCKLAND: JSONObject = {
|
|
2513
|
+
localTimezone: "Pacific/Auckland",
|
|
2514
|
+
entities: [{ type: "clientInfo", timezone: "Pacific/Auckland" }],
|
|
2515
|
+
rawLocalTimestamp: "2026-09-29T21:00:00.000+13:00",
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2518
|
+
function buildWithTimezone(
|
|
2519
|
+
timezone: string | undefined,
|
|
2520
|
+
budget?: number,
|
|
2521
|
+
): JSONObject {
|
|
2522
|
+
return MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2523
|
+
{
|
|
2524
|
+
choices: smallMaintenanceChoices(),
|
|
2525
|
+
budgetInBytes: budget ?? FIRST_BUDGET,
|
|
2526
|
+
timezone: timezone,
|
|
2527
|
+
},
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// The zone the submit handler reads a submit of this card in.
|
|
2532
|
+
function resolveSubmit(
|
|
2533
|
+
card: JSONObject,
|
|
2534
|
+
activity: JSONObject,
|
|
2535
|
+
): MicrosoftTeamsUserTimezone {
|
|
2536
|
+
return MicrosoftTeamsTimezone.resolve({
|
|
2537
|
+
activity: activity,
|
|
2538
|
+
timezoneFromCard: getSubmitData(card)["timezone"],
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
test.each([
|
|
2543
|
+
"America/New_York",
|
|
2544
|
+
"Asia/Kolkata",
|
|
2545
|
+
"Europe/Berlin",
|
|
2546
|
+
"Australia/Adelaide",
|
|
2547
|
+
"UTC",
|
|
2548
|
+
])(
|
|
2549
|
+
"%s is named on the card and carried to the submit, at every budget",
|
|
2550
|
+
(timezone: string) => {
|
|
2551
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2552
|
+
const card: JSONObject = buildWithTimezone(timezone, budget);
|
|
2553
|
+
|
|
2554
|
+
expect(getNotes(card)[0]).toBe(knownTimezoneNote(timezone));
|
|
2555
|
+
expect(getSubmitData(card)).toEqual({
|
|
2556
|
+
action: "SubmitNewScheduledMaintenance",
|
|
2557
|
+
timezone: timezone,
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
/*
|
|
2561
|
+
* What the card carries is what the submit handler reads the start
|
|
2562
|
+
* and end in, whether the submitting activity names no zone or
|
|
2563
|
+
* another one: the card told whoever filled it in that the times are
|
|
2564
|
+
* in this zone, and in a channel that may be someone elsewhere.
|
|
2565
|
+
*/
|
|
2566
|
+
for (const activity of [{}, SUBMITTED_FROM_AUCKLAND]) {
|
|
2567
|
+
expect(resolveSubmit(card, activity)).toEqual({
|
|
2568
|
+
timezone: timezone,
|
|
2569
|
+
label: timezone,
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
},
|
|
2574
|
+
);
|
|
2575
|
+
|
|
2576
|
+
test("a zone with stray spaces is carried trimmed", () => {
|
|
2577
|
+
const card: JSONObject = buildWithTimezone(" Europe/Berlin \n");
|
|
2578
|
+
|
|
2579
|
+
expect(getNotes(card)[0]).toBe(knownTimezoneNote("Europe/Berlin"));
|
|
2580
|
+
expect(getSubmitData(card)["timezone"]).toBe("Europe/Berlin");
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
test.each([
|
|
2584
|
+
"Mars/Olympus_Mons",
|
|
2585
|
+
"America/Atlantis",
|
|
2586
|
+
"not a time zone",
|
|
2587
|
+
"GMT+25",
|
|
2588
|
+
])(
|
|
2589
|
+
"an unknown zone (%p) gets the generic note, and is neither named nor carried",
|
|
2590
|
+
(timezone: string) => {
|
|
2591
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2592
|
+
const card: JSONObject = buildWithTimezone(timezone, budget);
|
|
2593
|
+
|
|
2594
|
+
expect(getNotes(card)[0]).toBe(GENERIC_TIMEZONE_NOTE);
|
|
2595
|
+
expect(getSubmitData(card)).toEqual({
|
|
2596
|
+
action: "SubmitNewScheduledMaintenance",
|
|
2597
|
+
});
|
|
2598
|
+
expect(JSON.stringify(card)).not.toContain(timezone);
|
|
2599
|
+
|
|
2600
|
+
// As the note says: read in the zone Teams reports for the submitter.
|
|
2601
|
+
expect(resolveSubmit(card, SUBMITTED_FROM_AUCKLAND)).toEqual({
|
|
2602
|
+
timezone: "Pacific/Auckland",
|
|
2603
|
+
label: "Pacific/Auckland",
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
},
|
|
2607
|
+
);
|
|
2608
|
+
|
|
2609
|
+
test.each([undefined, "", " "])(
|
|
2610
|
+
"no zone (%p) gets the generic note and carries none",
|
|
2611
|
+
(timezone: string | undefined) => {
|
|
2612
|
+
for (const budget of MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES) {
|
|
2613
|
+
const card: JSONObject = buildWithTimezone(timezone, budget);
|
|
2614
|
+
|
|
2615
|
+
expect(getNotes(card)[0]).toBe(GENERIC_TIMEZONE_NOTE);
|
|
2616
|
+
expect(getSubmitData(card)).toEqual({
|
|
2617
|
+
action: "SubmitNewScheduledMaintenance",
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
/*
|
|
2621
|
+
* As the note says: read in the zone Teams reports for the
|
|
2622
|
+
* submitter, or in UTC when it reports none.
|
|
2623
|
+
*/
|
|
2624
|
+
expect(resolveSubmit(card, SUBMITTED_FROM_AUCKLAND)).toEqual({
|
|
2625
|
+
timezone: "Pacific/Auckland",
|
|
2626
|
+
label: "Pacific/Auckland",
|
|
2627
|
+
});
|
|
2628
|
+
expect(resolveSubmit(card, {})).toEqual(MicrosoftTeamsTimezone.UTC);
|
|
2629
|
+
}
|
|
2630
|
+
},
|
|
2631
|
+
);
|
|
2632
|
+
});
|
|
2633
|
+
|
|
2634
|
+
describe("a list entry whose name is cut where an emoji sits", () => {
|
|
2635
|
+
/*
|
|
2636
|
+
* A name longer than a dropdown row is cut to
|
|
2637
|
+
* MICROSOFT_TEAMS_MAX_CHOICE_TITLE_LENGTH code units, the last of them "…"
|
|
2638
|
+
* (MicrosoftTeamsCardChoices.toChoices, as the fetchers read the lists).
|
|
2639
|
+
* Cut between the two halves of an emoji, the row showed a broken character
|
|
2640
|
+
* before the "…" (the card carried "\ud83c…"); the cut now keeps whole
|
|
2641
|
+
* characters. Pinned here as the user sees it, on both forms.
|
|
2642
|
+
*/
|
|
2643
|
+
function expectCutWholeCharacters(
|
|
2644
|
+
card: JSONObject,
|
|
2645
|
+
inputIds: Array<string>,
|
|
2646
|
+
): void {
|
|
2647
|
+
for (const inputId of inputIds) {
|
|
2648
|
+
const titles: Array<string> = getChoices(card, inputId).map(
|
|
2649
|
+
(choice: MicrosoftTeamsCardChoice) => {
|
|
2650
|
+
return choice.title;
|
|
2651
|
+
},
|
|
2652
|
+
);
|
|
2653
|
+
|
|
2654
|
+
expect(titles).toHaveLength(3);
|
|
2655
|
+
|
|
2656
|
+
for (const title of titles) {
|
|
2657
|
+
// Still cut short, and still the start of the name.
|
|
2658
|
+
expect(title.endsWith("…")).toBe(true);
|
|
2659
|
+
expect(
|
|
2660
|
+
title.startsWith(
|
|
2661
|
+
"x".repeat(MICROSOFT_TEAMS_MAX_CHOICE_TITLE_LENGTH - 2),
|
|
2662
|
+
),
|
|
2663
|
+
).toBe(true);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
expect(getTextsWithHalfAnEmoji(card)).toEqual([]);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
test("incident form: no half of an emoji in any dropdown", async () => {
|
|
2671
|
+
serveProject({
|
|
2672
|
+
monitors: buildRows({ table: 11, count: 3, nameOf: emojiAtTheCutName }),
|
|
2673
|
+
labels: buildRows({ table: 12, count: 3, nameOf: emojiAtTheCutName }),
|
|
2674
|
+
onCallDutyPolicies: buildRows({
|
|
2675
|
+
table: 13,
|
|
2676
|
+
count: 3,
|
|
2677
|
+
nameOf: emojiAtTheCutName,
|
|
2678
|
+
}),
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2681
|
+
const card: JSONObject =
|
|
2682
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
2683
|
+
choices:
|
|
2684
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(
|
|
2685
|
+
PROJECT_ID,
|
|
2686
|
+
),
|
|
2687
|
+
budgetInBytes: FIRST_BUDGET,
|
|
2688
|
+
});
|
|
2689
|
+
|
|
2690
|
+
expectCutWholeCharacters(card, [
|
|
2691
|
+
"incidentMonitors",
|
|
2692
|
+
"onCallDutyPolicies",
|
|
2693
|
+
"labels",
|
|
2694
|
+
]);
|
|
2695
|
+
});
|
|
2696
|
+
|
|
2697
|
+
test("maintenance form: no half of an emoji in any dropdown", async () => {
|
|
2698
|
+
serveProject({
|
|
2699
|
+
monitors: buildRows({ table: 11, count: 3, nameOf: emojiAtTheCutName }),
|
|
2700
|
+
labels: buildRows({ table: 12, count: 3, nameOf: emojiAtTheCutName }),
|
|
2701
|
+
onCallDutyPolicies: SMALL_POLICY_ROWS,
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
const card: JSONObject =
|
|
2705
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2706
|
+
{
|
|
2707
|
+
choices:
|
|
2708
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
2709
|
+
PROJECT_ID,
|
|
2710
|
+
),
|
|
2711
|
+
budgetInBytes: FIRST_BUDGET,
|
|
2712
|
+
},
|
|
2713
|
+
);
|
|
2714
|
+
|
|
2715
|
+
expectCutWholeCharacters(card, ["scheduledMaintenanceMonitors", "labels"]);
|
|
2716
|
+
});
|
|
2717
|
+
});
|
|
2718
|
+
|
|
2719
|
+
describe("the legacy builders, which read the project themselves", () => {
|
|
2720
|
+
test("buildNewIncidentCard(projectId) fits a huge project into the first budget", async () => {
|
|
2721
|
+
serveHugeProject();
|
|
2722
|
+
|
|
2723
|
+
const card: JSONObject =
|
|
2724
|
+
await MicrosoftTeamsIncidentActions.buildNewIncidentCard(PROJECT_ID);
|
|
2725
|
+
|
|
2726
|
+
expectWithinBudget(card, FIRST_BUDGET);
|
|
2727
|
+
expect(getChoices(card, "incidentSeverity")).toHaveLength(5);
|
|
2728
|
+
expect(getInputOrFail(card, "incidentTitle")).not.toHaveProperty("value");
|
|
2729
|
+
|
|
2730
|
+
const shownMonitors: number = getChoices(card, "incidentMonitors").length;
|
|
2731
|
+
|
|
2732
|
+
expect(shownMonitors).toBeGreaterThan(0);
|
|
2733
|
+
expect(getNotes(card)).toHaveLength(3);
|
|
2734
|
+
expect(getNotes(card)[0]).toBe(
|
|
2735
|
+
showingNote({
|
|
2736
|
+
shown: shownMonitors,
|
|
2737
|
+
total: HUGE_MONITOR_COUNT,
|
|
2738
|
+
noun: "monitors",
|
|
2739
|
+
hint: INCIDENT_ADD_LATER_HINT,
|
|
2740
|
+
}),
|
|
2741
|
+
);
|
|
2742
|
+
// It is given no link to offer.
|
|
2743
|
+
expect(getOpenUrlActions(card)).toEqual([]);
|
|
2744
|
+
|
|
2745
|
+
// The card the bot would send first for the same lists.
|
|
2746
|
+
expect(card).toEqual(
|
|
2747
|
+
MicrosoftTeamsIncidentActions.buildNewIncidentCardForBudget({
|
|
2748
|
+
choices:
|
|
2749
|
+
await MicrosoftTeamsIncidentActions.getNewIncidentFormChoices(
|
|
2750
|
+
PROJECT_ID,
|
|
2751
|
+
),
|
|
2752
|
+
budgetInBytes: FIRST_BUDGET,
|
|
2753
|
+
}),
|
|
2754
|
+
);
|
|
2755
|
+
});
|
|
2756
|
+
|
|
2757
|
+
test("buildNewIncidentCard prefills the title it is given, trimmed", async () => {
|
|
2758
|
+
serveHugeProject();
|
|
2759
|
+
|
|
2760
|
+
const card: JSONObject =
|
|
2761
|
+
await MicrosoftTeamsIncidentActions.buildNewIncidentCard(PROJECT_ID, {
|
|
2762
|
+
initialTitle: " Checkout is down ",
|
|
2763
|
+
});
|
|
2764
|
+
|
|
2765
|
+
expectWithinBudget(card, FIRST_BUDGET);
|
|
2766
|
+
expect(getInputOrFail(card, "incidentTitle")["value"]).toBe(
|
|
2767
|
+
"Checkout is down",
|
|
2768
|
+
);
|
|
2769
|
+
});
|
|
2770
|
+
|
|
2771
|
+
test("buildNewIncidentCard lists a small project whole", async () => {
|
|
2772
|
+
serveSmallProject();
|
|
2773
|
+
|
|
2774
|
+
const card: JSONObject =
|
|
2775
|
+
await MicrosoftTeamsIncidentActions.buildNewIncidentCard(PROJECT_ID);
|
|
2776
|
+
|
|
2777
|
+
expect(getChoices(card, "incidentSeverity")).toEqual(
|
|
2778
|
+
listOf(SEVERITY_ROWS).choices,
|
|
2779
|
+
);
|
|
2780
|
+
expect(getChoices(card, "incidentMonitors")).toEqual(
|
|
2781
|
+
listOf(SMALL_MONITOR_ROWS).choices,
|
|
2782
|
+
);
|
|
2783
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
2784
|
+
listOf(MONITOR_STATUS_ROWS).choices,
|
|
2785
|
+
);
|
|
2786
|
+
expect(getChoices(card, "onCallDutyPolicies")).toEqual(
|
|
2787
|
+
listOf(SMALL_POLICY_ROWS).choices,
|
|
2788
|
+
);
|
|
2789
|
+
expect(getChoices(card, "labels")).toEqual(
|
|
2790
|
+
listOf(SMALL_LABEL_ROWS).choices,
|
|
2791
|
+
);
|
|
2792
|
+
expect(getNotes(card)).toEqual([]);
|
|
2793
|
+
});
|
|
2794
|
+
|
|
2795
|
+
test("buildNewScheduledMaintenanceCard(projectId) fits a huge project into the first budget", async () => {
|
|
2796
|
+
serveHugeProject();
|
|
2797
|
+
|
|
2798
|
+
const card: JSONObject =
|
|
2799
|
+
await MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCard(
|
|
2800
|
+
PROJECT_ID,
|
|
2801
|
+
);
|
|
2802
|
+
|
|
2803
|
+
expectWithinBudget(card, FIRST_BUDGET);
|
|
2804
|
+
expect(
|
|
2805
|
+
getInputOrFail(card, "scheduledMaintenanceTitle"),
|
|
2806
|
+
).not.toHaveProperty("value");
|
|
2807
|
+
|
|
2808
|
+
const shownMonitors: number = getChoices(
|
|
2809
|
+
card,
|
|
2810
|
+
"scheduledMaintenanceMonitors",
|
|
2811
|
+
).length;
|
|
2812
|
+
|
|
2813
|
+
expect(shownMonitors).toBeGreaterThan(0);
|
|
2814
|
+
expect(getNotes(card)).toHaveLength(3);
|
|
2815
|
+
expect(getNotes(card)[0]).toBe(GENERIC_TIMEZONE_NOTE);
|
|
2816
|
+
expect(getNotes(card)[1]).toBe(
|
|
2817
|
+
showingNote({
|
|
2818
|
+
shown: shownMonitors,
|
|
2819
|
+
total: HUGE_MONITOR_COUNT,
|
|
2820
|
+
noun: "monitors",
|
|
2821
|
+
hint: MAINTENANCE_ADD_LATER_HINT,
|
|
2822
|
+
}),
|
|
2823
|
+
);
|
|
2824
|
+
expect(getSubmitData(card)).toEqual({
|
|
2825
|
+
action: "SubmitNewScheduledMaintenance",
|
|
2826
|
+
});
|
|
2827
|
+
expect(getOpenUrlActions(card)).toEqual([]);
|
|
2828
|
+
|
|
2829
|
+
expect(card).toEqual(
|
|
2830
|
+
MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCardForBudget(
|
|
2831
|
+
{
|
|
2832
|
+
choices:
|
|
2833
|
+
await MicrosoftTeamsScheduledMaintenanceActions.getNewScheduledMaintenanceFormChoices(
|
|
2834
|
+
PROJECT_ID,
|
|
2835
|
+
),
|
|
2836
|
+
budgetInBytes: FIRST_BUDGET,
|
|
2837
|
+
},
|
|
2838
|
+
),
|
|
2839
|
+
);
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2842
|
+
test("buildNewScheduledMaintenanceCard prefills the title and names the time zone it is given", async () => {
|
|
2843
|
+
serveHugeProject();
|
|
2844
|
+
|
|
2845
|
+
const card: JSONObject =
|
|
2846
|
+
await MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCard(
|
|
2847
|
+
PROJECT_ID,
|
|
2848
|
+
{ initialTitle: " Database upgrade ", timezone: "Asia/Kolkata" },
|
|
2849
|
+
);
|
|
2850
|
+
|
|
2851
|
+
expectWithinBudget(card, FIRST_BUDGET);
|
|
2852
|
+
expect(getInputOrFail(card, "scheduledMaintenanceTitle")["value"]).toBe(
|
|
2853
|
+
"Database upgrade",
|
|
2854
|
+
);
|
|
2855
|
+
expect(getNotes(card)[0]).toBe(knownTimezoneNote("Asia/Kolkata"));
|
|
2856
|
+
expect(getSubmitData(card)).toEqual({
|
|
2857
|
+
action: "SubmitNewScheduledMaintenance",
|
|
2858
|
+
timezone: "Asia/Kolkata",
|
|
2859
|
+
});
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
test("buildNewScheduledMaintenanceCard lists a small project whole", async () => {
|
|
2863
|
+
serveSmallProject();
|
|
2864
|
+
|
|
2865
|
+
const card: JSONObject =
|
|
2866
|
+
await MicrosoftTeamsScheduledMaintenanceActions.buildNewScheduledMaintenanceCard(
|
|
2867
|
+
PROJECT_ID,
|
|
2868
|
+
);
|
|
2869
|
+
|
|
2870
|
+
expect(getChoices(card, "scheduledMaintenanceMonitors")).toEqual(
|
|
2871
|
+
listOf(SMALL_MONITOR_ROWS).choices,
|
|
2872
|
+
);
|
|
2873
|
+
expect(getChoices(card, "monitorStatus")).toEqual(
|
|
2874
|
+
listOf(MONITOR_STATUS_ROWS).choices,
|
|
2875
|
+
);
|
|
2876
|
+
expect(getChoices(card, "labels")).toEqual(
|
|
2877
|
+
listOf(SMALL_LABEL_ROWS).choices,
|
|
2878
|
+
);
|
|
2879
|
+
expect(getNotes(card)).toEqual([GENERIC_TIMEZONE_NOTE]);
|
|
2880
|
+
expect(getSubmitData(card)).toEqual({
|
|
2881
|
+
action: "SubmitNewScheduledMaintenance",
|
|
2882
|
+
});
|
|
2883
|
+
});
|
|
2884
|
+
});
|