@oneuptime/common 14.0.10 → 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/CephCluster.ts +12 -0
- package/Models/DatabaseModels/CloudResource.ts +12 -0
- package/Models/DatabaseModels/DatabaseServer.ts +12 -0
- package/Models/DatabaseModels/DockerHost.ts +12 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +12 -0
- 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 +12 -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 +14 -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 +12 -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 +12 -0
- package/Models/DatabaseModels/RumApplication.ts +12 -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 +12 -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/IncidentFormAPI.ts +376 -0
- package/Server/API/ProjectOIDC.ts +16 -25
- package/Server/API/ProjectSSO.ts +16 -24
- 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/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 +8 -0
- package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
- package/Server/Middleware/UserAuthorization.ts +16 -38
- package/Server/Services/AIService.ts +10 -13
- 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/OpenTelemetryIngestService.ts +37 -4
- package/Server/Services/ProjectService.ts +0 -59
- package/Server/Services/StatusPagePrivateUserService.ts +1 -4
- package/Server/Services/StatusPageService.ts +1 -51
- package/Server/Services/TeamComplianceSettingService.ts +163 -42
- package/Server/Services/UserNotificationRuleService.ts +8 -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 +7 -1
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +34 -14
- package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +69 -24
- package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +9 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +53 -10
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +27 -10
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +27 -10
- 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/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/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/Incident.ts +3 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +3 -1
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +10 -1
- 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 +174 -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 +5 -0
- 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/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/ResourceAiAgentAttentionServerParity.test.ts +555 -0
- package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +298 -9
- package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +453 -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/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 +28 -9
- package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
- package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -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/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/{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/AddIncidentFormsMigration.test.ts +438 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
- package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
- package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
- 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/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/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/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
- 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/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 +266 -25
- 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/RemediationExecutionRunner.test.ts +14 -25
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +297 -15
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +10 -6
- 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/EditionEnforcement.test.ts +107 -147
- 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/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/Types/AI/AIAgentRunLimits.test.ts +255 -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/ResourceAiAgent/ResourceAiAccess.test.ts +5 -3
- 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/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -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/Types/AI/AIAgentRunLimits.ts +146 -0
- 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/Permission.ts +310 -0
- package/Types/ResourceAiAgent/ResourceAiAccess.ts +10 -4
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
- package/Types/Rules/RuleRun.ts +4 -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/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/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/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/CephCluster.js +12 -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 +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +12 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.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 +12 -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 +14 -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 +12 -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 +12 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
- package/build/dist/Models/DatabaseModels/RumApplication.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 +12 -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/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/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/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 +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.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/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/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/OpenTelemetryIngestService.js +25 -4
- package/build/dist/Server/Services/OpenTelemetryIngestService.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/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/UserNotificationRuleService.js +8 -1
- package/build/dist/Server/Services/UserNotificationRuleService.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 +7 -2
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +29 -14
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +38 -14
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +2 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +40 -10
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +19 -7
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +19 -7
- 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/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/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/Incident.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
- package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
- 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/Permission.js +274 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +10 -4
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -1
- 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/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/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/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/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,1339 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LEXED_TEXT_MAX_LENGTH,
|
|
3
|
+
neutralizeChatControlSequences,
|
|
4
|
+
neutralizeMarkdownImagesAndDiagrams,
|
|
5
|
+
neutralizeUntrustedMarkdown,
|
|
6
|
+
neutralizeUntrustedPlainText,
|
|
7
|
+
} from "../../../Utils/Markdown/UntrustedMarkdown";
|
|
8
|
+
import SlackUtil from "../../../Server/Utils/Workspace/Slack/Slack";
|
|
9
|
+
import {
|
|
10
|
+
INCIDENT_FORM_CUSTOM_FIELD_TEXT_MAX_LENGTH,
|
|
11
|
+
INCIDENT_FORM_DESCRIPTION_MAX_LENGTH,
|
|
12
|
+
} from "../../../Types/Incident/IncidentFormPublic";
|
|
13
|
+
import { renderAsDashboard } from "./DashboardMarkdownRenderer";
|
|
14
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
15
|
+
import { Lexer, Token, Tokens, marked } from "marked";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Markdown an incident form's reporter wrote - somebody with no account,
|
|
19
|
+
* only the form's link - is stored as an incident's own text, which nobody
|
|
20
|
+
* reads over before it is posted to Slack and Teams and rendered for every
|
|
21
|
+
* responder and in every owner's email. Three things in it act on their own
|
|
22
|
+
* there: chat control sequences (Slack mentions), images (fetched from the
|
|
23
|
+
* reporter's server by every reader) and mermaid diagrams (run in the
|
|
24
|
+
* reader's browser).
|
|
25
|
+
*
|
|
26
|
+
* What is pinned, for every shape of each:
|
|
27
|
+
*
|
|
28
|
+
* - nothing survives that acts on its own - checked with a real lexer
|
|
29
|
+
* (marked, as the emails are rendered), a real Slack conversion, the
|
|
30
|
+
* dashboard's own parser (micromark with its GitHub extensions, run in
|
|
31
|
+
* a child process - jest stubs react-markdown and remark-gfm), and on
|
|
32
|
+
* the characters themselves: no "![" is left that is not escaped and
|
|
33
|
+
* that anything could complete, outside fenced code every renderer
|
|
34
|
+
* reads as code, and no info string reads "mermaid", character
|
|
35
|
+
* references decoded. The characters matter because the parsers do not
|
|
36
|
+
* read every text alike (see "whatever renderer reads it" below);
|
|
37
|
+
* - everything else reads exactly as typed: text and links around a
|
|
38
|
+
* neutralised image are untouched, code with image syntax in it that
|
|
39
|
+
* something could complete only gains an invisible word joiner - unless
|
|
40
|
+
* it is fenced code every renderer reads as code, which is left byte for
|
|
41
|
+
* byte, as is code without any - and a mention looks the same once the
|
|
42
|
+
* joiner is ignored;
|
|
43
|
+
* - it takes time in proportion to the text: marked, whose worst shapes
|
|
44
|
+
* take seconds at the 20000 characters a description may hold, reads
|
|
45
|
+
* only a text of at most LEXED_TEXT_MAX_LENGTH characters.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const WORD_JOINER: string = "\u2060";
|
|
49
|
+
|
|
50
|
+
// Every token the lexer makes of the text, depth first.
|
|
51
|
+
function tokensOf(markdown: string): Array<Token> {
|
|
52
|
+
const all: Array<Token> = [];
|
|
53
|
+
|
|
54
|
+
marked.walkTokens(
|
|
55
|
+
new Lexer({ gfm: true, breaks: false, pedantic: false }).lex(markdown),
|
|
56
|
+
(token: Token): void => {
|
|
57
|
+
all.push(token);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return all;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function images(markdown: string): Array<Tokens.Image> {
|
|
65
|
+
return tokensOf(markdown).filter((token: Token): boolean => {
|
|
66
|
+
return token.type === "image";
|
|
67
|
+
}) as Array<Tokens.Image>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function links(markdown: string): Array<Tokens.Link> {
|
|
71
|
+
return tokensOf(markdown).filter((token: Token): boolean => {
|
|
72
|
+
return token.type === "link";
|
|
73
|
+
}) as Array<Tokens.Link>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function codeBlocks(markdown: string): Array<Tokens.Code> {
|
|
77
|
+
return tokensOf(markdown).filter((token: Token): boolean => {
|
|
78
|
+
return token.type === "code";
|
|
79
|
+
}) as Array<Tokens.Code>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function codeSpans(markdown: string): Array<string> {
|
|
83
|
+
return tokensOf(markdown)
|
|
84
|
+
.filter((token: Token): boolean => {
|
|
85
|
+
return token.type === "codespan";
|
|
86
|
+
})
|
|
87
|
+
.map((token: Token): string => {
|
|
88
|
+
return (token as Tokens.Codespan).text;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// What marked renders, as the owners' and on-call emails are rendered.
|
|
93
|
+
function html(markdown: string): string {
|
|
94
|
+
return marked.parse(markdown, { async: false }) as string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The sections the bot posts to Slack for the text.
|
|
98
|
+
function slackText(markdown: string): string {
|
|
99
|
+
return JSON.stringify(
|
|
100
|
+
SlackUtil.getMarkdownBlocks({
|
|
101
|
+
payloadMarkdownBlock: {
|
|
102
|
+
_type: "WorkspacePayloadMarkdown",
|
|
103
|
+
text: markdown,
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function withoutJoiners(text: string): string {
|
|
110
|
+
return text.split(WORD_JOINER).join("");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* The text with its numeric character references decoded, as CommonMark
|
|
115
|
+
* decodes them in an info string: ```mermaid is a mermaid fence.
|
|
116
|
+
*/
|
|
117
|
+
function withReferencesDecoded(text: string): string {
|
|
118
|
+
return text.replace(
|
|
119
|
+
/&#(?:[xX]([0-9a-fA-F]+)|([0-9]+));/g,
|
|
120
|
+
(_reference: string, hex?: string, decimal?: string): string => {
|
|
121
|
+
const codePoint: number = hex ? parseInt(hex, 16) : Number(decimal);
|
|
122
|
+
|
|
123
|
+
return codePoint > 0 && codePoint <= 0x10ffff
|
|
124
|
+
? String.fromCodePoint(codePoint)
|
|
125
|
+
: "\uFFFD";
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const HTML_IMAGE_PATTERN: RegExp = /<img/;
|
|
131
|
+
const MERMAID_CLASS_PATTERN: RegExp = /language-mermaid/i;
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* What the dashboard's parser makes of each text: no image, and no code
|
|
135
|
+
* block whose class holds "language-mermaid" - neither for a language that
|
|
136
|
+
* decodes to "mermaid" nor for one such as "-language-mermaid", which a
|
|
137
|
+
* viewer matching the class loosely would read as mermaid too.
|
|
138
|
+
*/
|
|
139
|
+
function expectInertInDashboard(texts: Array<string>): void {
|
|
140
|
+
const htmls: Array<string> = renderAsDashboard(texts);
|
|
141
|
+
|
|
142
|
+
texts.forEach((text: string, index: number): void => {
|
|
143
|
+
const html: string = htmls[index]!;
|
|
144
|
+
|
|
145
|
+
expect({
|
|
146
|
+
text: text,
|
|
147
|
+
image: HTML_IMAGE_PATTERN.test(html),
|
|
148
|
+
diagram: MERMAID_CLASS_PATTERN.test(html),
|
|
149
|
+
}).toEqual({ text: text, image: false, diagram: false });
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// How long neutralising one text may take, however it is shaped.
|
|
154
|
+
const TIME_BOUND_IN_MS: number = 1000;
|
|
155
|
+
|
|
156
|
+
// The unit repeated, cut to exactly this length.
|
|
157
|
+
function repeatTo(unit: string, length: number): string {
|
|
158
|
+
return unit.repeat(Math.ceil(length / unit.length)).slice(0, length);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
* The slowest shapes found for a text of a given length. On those marked
|
|
163
|
+
* takes seconds for: emphasis next to image syntax, a run of unclosed
|
|
164
|
+
* emphasis, brackets and emphasis - the markers tagging each place make
|
|
165
|
+
* some of them worse still. Each gives a text of exactly the length asked,
|
|
166
|
+
* in which something could complete every "![" - so none may be left open.
|
|
167
|
+
*/
|
|
168
|
+
const WORST_CASE_SHAPES: Array<[string, (length: number) => string]> = [
|
|
169
|
+
[
|
|
170
|
+
"back-to-back images",
|
|
171
|
+
(length: number): string => {
|
|
172
|
+
return repeatTo("", length);
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
"image openers, closed once at the end",
|
|
177
|
+
(length: number): string => {
|
|
178
|
+
return `${repeatTo("`;
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
"empty reference images before a definition",
|
|
183
|
+
(length: number): string => {
|
|
184
|
+
return `${repeatTo("![][a]", length - 8)}\n\n[a]: u`;
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
[
|
|
188
|
+
"image openers before a definition",
|
|
189
|
+
(length: number): string => {
|
|
190
|
+
return `${repeatTo("![a]", length - 8)}\n\n[a]: u`;
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"images in strong emphasis",
|
|
195
|
+
(length: number): string => {
|
|
196
|
+
return repeatTo("****", length);
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
"images in underscore emphasis",
|
|
201
|
+
(length: number): string => {
|
|
202
|
+
return repeatTo("____", length);
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
"images with alt text in emphasis",
|
|
207
|
+
(length: number): string => {
|
|
208
|
+
return repeatTo("__", length);
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
[
|
|
212
|
+
"text and images in emphasis",
|
|
213
|
+
(length: number): string => {
|
|
214
|
+
return repeatTo("**x**", length);
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
"brackets in emphasis, then one image",
|
|
219
|
+
(length: number): string => {
|
|
220
|
+
return `${repeatTo("**a](b)**", length - 7)}`;
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
"emphasised image openers before a definition",
|
|
225
|
+
(length: number): string => {
|
|
226
|
+
return `${repeatTo("**![", length - 8)}\n\n[a]: b`;
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
[
|
|
230
|
+
"one image, then unclosed emphasis",
|
|
231
|
+
(length: number): string => {
|
|
232
|
+
return ` ${repeatTo("*a ", length - 8)}`;
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
[
|
|
236
|
+
"image openers in links",
|
|
237
|
+
(length: number): string => {
|
|
238
|
+
return `${repeatTo("[}](u)`;
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
"a run of backticks",
|
|
243
|
+
(length: number): string => {
|
|
244
|
+
return "`".repeat(length);
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
"a run of tildes",
|
|
249
|
+
(length: number): string => {
|
|
250
|
+
return "~".repeat(length);
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
[
|
|
254
|
+
"mermaid fences spelled with references",
|
|
255
|
+
(length: number): string => {
|
|
256
|
+
return repeatTo("```mermaid\n", length);
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
[
|
|
260
|
+
"labels after text before a colon, then an image",
|
|
261
|
+
(length: number): string => {
|
|
262
|
+
return `${repeatTo("x[a]:", length - 9)}\n\n`;
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
[
|
|
266
|
+
"images in code spans",
|
|
267
|
+
(length: number): string => {
|
|
268
|
+
return repeatTo("`` ", length);
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
afterEach(() => {
|
|
274
|
+
jest.restoreAllMocks();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
/*
|
|
278
|
+
* Where each "![" whose "!" is not escaped starts - an image can only begin
|
|
279
|
+
* at one of these, whichever parser reads the text.
|
|
280
|
+
*/
|
|
281
|
+
function openImageSyntax(text: string): Array<number> {
|
|
282
|
+
const positions: Array<number> = [];
|
|
283
|
+
|
|
284
|
+
for (
|
|
285
|
+
let position: number = text.indexOf("![");
|
|
286
|
+
position !== -1;
|
|
287
|
+
position = text.indexOf("![", position + 1)
|
|
288
|
+
) {
|
|
289
|
+
let backslashes: number = 0;
|
|
290
|
+
|
|
291
|
+
while (
|
|
292
|
+
position - backslashes - 1 >= 0 &&
|
|
293
|
+
text[position - backslashes - 1] === "\\"
|
|
294
|
+
) {
|
|
295
|
+
backslashes++;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (backslashes % 2 === 0) {
|
|
299
|
+
positions.push(position);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return positions;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Nothing in the text is fetched or run when it is shown.
|
|
307
|
+
function expectInert(markdown: string): void {
|
|
308
|
+
expect(images(markdown)).toEqual([]);
|
|
309
|
+
expect(html(markdown)).not.toContain("<img");
|
|
310
|
+
expect(
|
|
311
|
+
codeBlocks(markdown).filter((block: Tokens.Code): boolean => {
|
|
312
|
+
return (block.lang || "").trim().toLowerCase().startsWith("mermaid");
|
|
313
|
+
}),
|
|
314
|
+
).toEqual([]);
|
|
315
|
+
/*
|
|
316
|
+
* No fence of any renderer can open with "mermaid" as its language, as
|
|
317
|
+
* typed or spelled with character references.
|
|
318
|
+
*/
|
|
319
|
+
expect(withReferencesDecoded(markdown).toLowerCase()).not.toMatch(
|
|
320
|
+
/(`{3,}|~{3,})[ \t]*mermaid/,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/*
|
|
325
|
+
* As expectInert, and on the characters: no image syntax is left open at
|
|
326
|
+
* all, so no parser - however it reads the text around it - finds an image.
|
|
327
|
+
*/
|
|
328
|
+
function expectNoOpenImage(markdown: string): void {
|
|
329
|
+
expectInert(markdown);
|
|
330
|
+
expect(openImageSyntax(markdown)).toEqual([]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
describe("neutralizeChatControlSequences", () => {
|
|
334
|
+
test.each([
|
|
335
|
+
["<!channel> Checkout is down"],
|
|
336
|
+
["<!here>"],
|
|
337
|
+
["<!everyone> please look"],
|
|
338
|
+
["<!subteam^S0123ABC> on-call"],
|
|
339
|
+
["<!subteam^S0123ABC|@sre>"],
|
|
340
|
+
["<!date^1392734382^{date_short}|Feb 18, 2014>"],
|
|
341
|
+
["hi <@U0123ABC>"],
|
|
342
|
+
["<@W0123ABC|jane>"],
|
|
343
|
+
["see <#C0123ABC>"],
|
|
344
|
+
["see <#C0123ABC|general>"],
|
|
345
|
+
])("breaks %j, and it still reads as typed", (text: string) => {
|
|
346
|
+
const neutralized: string = neutralizeChatControlSequences(text);
|
|
347
|
+
|
|
348
|
+
expect(neutralized).not.toMatch(/<[!@#]/);
|
|
349
|
+
expect(neutralized.split(WORD_JOINER).join("")).toBe(text);
|
|
350
|
+
expect(neutralized).toContain(`<${WORD_JOINER}`);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("breaks every sequence in the text, code spans and blocks included", () => {
|
|
354
|
+
const text: string =
|
|
355
|
+
"<!here> and <@U1> and <#C1>\n\n`<!channel>`\n\n```\n<!everyone>\n```";
|
|
356
|
+
|
|
357
|
+
const neutralized: string = neutralizeChatControlSequences(text);
|
|
358
|
+
|
|
359
|
+
expect(neutralized).not.toMatch(/<[!@#]/);
|
|
360
|
+
expect(neutralized.split(WORD_JOINER).join("")).toBe(text);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test.each([
|
|
364
|
+
["a plain sentence"],
|
|
365
|
+
["a < b and c > d"],
|
|
366
|
+
["<https://status.example.com>"],
|
|
367
|
+
["<jane@example.com>"],
|
|
368
|
+
["**bold** and _italic_"],
|
|
369
|
+
["<div>raw</div>"],
|
|
370
|
+
["a<b"],
|
|
371
|
+
["<"],
|
|
372
|
+
["<>"],
|
|
373
|
+
["< !channel>"],
|
|
374
|
+
["!here @jane #general"],
|
|
375
|
+
[""],
|
|
376
|
+
// Pasted HTML and PowerShell: none of it is a sequence Slack acts on.
|
|
377
|
+
["<!DOCTYPE html>"],
|
|
378
|
+
["<!doctype html>"],
|
|
379
|
+
["<!-- upstream 502 -->"],
|
|
380
|
+
["<![CDATA[a < b]]>"],
|
|
381
|
+
['<!ENTITY copy "©">'],
|
|
382
|
+
["<# PowerShell block comment #>"],
|
|
383
|
+
["<#\n.SYNOPSIS\n Restarts the pool.\n#>"],
|
|
384
|
+
])("leaves %j exactly as it is", (text: string) => {
|
|
385
|
+
expect(neutralizeChatControlSequences(text)).toBe(text);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
/*
|
|
389
|
+
* A reporter pasting the page a proxy served into the description: a
|
|
390
|
+
* responder copies it out of the report to reproduce the problem, and a
|
|
391
|
+
* stored invisible character would turn "<!DOCTYPE" and "<!--" into text
|
|
392
|
+
* in the browser it is pasted into.
|
|
393
|
+
*/
|
|
394
|
+
test("stores an HTML page pasted in a code block exactly as typed", () => {
|
|
395
|
+
const text: string =
|
|
396
|
+
"```html\n<!DOCTYPE html>\n<!-- upstream 502 -->\n<![CDATA[x]]>\n```";
|
|
397
|
+
|
|
398
|
+
expect(neutralizeUntrustedMarkdown(text)).toBe(text);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("still breaks a mention right after a comment or a declaration", () => {
|
|
402
|
+
expect(
|
|
403
|
+
neutralizeChatControlSequences(
|
|
404
|
+
"<!-- note --><!here> <!DOCTYPE html><@U0123ABC>",
|
|
405
|
+
),
|
|
406
|
+
).toBe(
|
|
407
|
+
`<!-- note --><${WORD_JOINER}!here> <!DOCTYPE html><${WORD_JOINER}@U0123ABC>`,
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("is idempotent", () => {
|
|
412
|
+
const once: string = neutralizeChatControlSequences("<!channel> <@U1>");
|
|
413
|
+
|
|
414
|
+
expect(neutralizeChatControlSequences(once)).toBe(once);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test.each([[null], [undefined]])(
|
|
418
|
+
"turns %p into an empty text",
|
|
419
|
+
(value: null | undefined) => {
|
|
420
|
+
expect(neutralizeChatControlSequences(value)).toBe("");
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
/*
|
|
425
|
+
* What decides it: the text as the bot posts it to Slack. slackify keeps
|
|
426
|
+
* <!here> and <@U1> as they are - in text and in code - and Slack reads
|
|
427
|
+
* them as mentions; with the word joiner the "<" is escaped instead.
|
|
428
|
+
*/
|
|
429
|
+
test("no Slack mention survives the bot's Markdown conversion", () => {
|
|
430
|
+
const text: string =
|
|
431
|
+
"**<!channel> Checkout is down**\n\n<!here> <!everyone> <!subteam^S1> <@U0123ABC> <#C0123ABC>\n\n`<!here>`\n\n```\n<@U1>\n```";
|
|
432
|
+
|
|
433
|
+
const before: string = JSON.stringify(
|
|
434
|
+
SlackUtil.getMarkdownBlocks({
|
|
435
|
+
payloadMarkdownBlock: { _type: "WorkspacePayloadMarkdown", text: text },
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
const after: string = JSON.stringify(
|
|
439
|
+
SlackUtil.getMarkdownBlocks({
|
|
440
|
+
payloadMarkdownBlock: {
|
|
441
|
+
_type: "WorkspacePayloadMarkdown",
|
|
442
|
+
text: neutralizeChatControlSequences(text),
|
|
443
|
+
},
|
|
444
|
+
}),
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
// The unneutralised text really does carry live mentions.
|
|
448
|
+
expect(before).toMatch(/<(![a-z]|@[A-Z0-9]|#C)/);
|
|
449
|
+
expect(after).not.toMatch(/<(![a-z]|@[A-Z0-9]|#C)/);
|
|
450
|
+
expect(after).toContain("<");
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
describe("neutralizeMarkdownImagesAndDiagrams - images", () => {
|
|
455
|
+
test("turns an image into a link to the same address", () => {
|
|
456
|
+
const text: string = "Look:  here";
|
|
457
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
458
|
+
|
|
459
|
+
expect(result).toBe("Look: \\ here");
|
|
460
|
+
expectNoOpenImage(result);
|
|
461
|
+
expect(
|
|
462
|
+
links(result).map((link: Tokens.Link): string => {
|
|
463
|
+
return link.href;
|
|
464
|
+
}),
|
|
465
|
+
).toEqual(["https://t.example/p.png"]);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("gives an image without alt text its address as the link's text, so the link can be seen", () => {
|
|
469
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(
|
|
470
|
+
"",
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
expect(result).toBe(
|
|
474
|
+
"\\",
|
|
475
|
+
);
|
|
476
|
+
expectNoOpenImage(result);
|
|
477
|
+
expect(links(result)[0]!.text).toBe("https://t.example/p.png");
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("shortens a long address used as the link's text, and escapes it to read as typed", () => {
|
|
481
|
+
const address: string = `https://t.example/${"a_b".repeat(40)}.png`;
|
|
482
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(
|
|
483
|
+
``,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
expectNoOpenImage(result);
|
|
487
|
+
expect(links(result)[0]!.href).toBe(address);
|
|
488
|
+
// The underscores are escaped in the source, and read as typed.
|
|
489
|
+
expect(html(result)).toContain(`>${address.slice(0, 80)}...</a>`);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("gives an image with no address a plain word to click", () => {
|
|
493
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams("![]()");
|
|
494
|
+
|
|
495
|
+
expect(result).toBe("\\![image]()");
|
|
496
|
+
expectNoOpenImage(result);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test.each([
|
|
500
|
+
["an image with a title", ''],
|
|
501
|
+
["an image in angle brackets", ""],
|
|
502
|
+
[
|
|
503
|
+
"a full reference image",
|
|
504
|
+
"See ![x][pic]\n\n[pic]: https://t.example/p.png",
|
|
505
|
+
],
|
|
506
|
+
[
|
|
507
|
+
"a collapsed reference image",
|
|
508
|
+
"See ![pic][]\n\n[pic]: https://t.example/p.png",
|
|
509
|
+
],
|
|
510
|
+
[
|
|
511
|
+
"a shortcut reference image",
|
|
512
|
+
"See ![pic]\n\n[pic]: https://t.example/p.png",
|
|
513
|
+
],
|
|
514
|
+
[
|
|
515
|
+
"a reference image defined before it",
|
|
516
|
+
"[pic]: https://t.example/p.png\n\n![pic]",
|
|
517
|
+
],
|
|
518
|
+
/*
|
|
519
|
+
* marked takes a task list item's box off before it reads the item, so
|
|
520
|
+
* a definition can follow the box on its line.
|
|
521
|
+
*/
|
|
522
|
+
[
|
|
523
|
+
"a reference image defined in a task list item",
|
|
524
|
+
"- [ ] [pic]: https://t.example/p.png\n\n![pic]",
|
|
525
|
+
],
|
|
526
|
+
[
|
|
527
|
+
"a reference image defined in a ticked task list item in a block quote",
|
|
528
|
+
"> 1. [x] [pic]: https://t.example/p.png\n\nSee ![pic] here",
|
|
529
|
+
],
|
|
530
|
+
["an image in a heading", "# Down "],
|
|
531
|
+
["an image in bold", "****"],
|
|
532
|
+
["an image in a block quote", "> quoted\n> "],
|
|
533
|
+
["an image in a nested block quote", "> > "],
|
|
534
|
+
["an image in a list item", "- one\n- "],
|
|
535
|
+
[
|
|
536
|
+
"an image in a nested list item",
|
|
537
|
+
"1. one\n - ",
|
|
538
|
+
],
|
|
539
|
+
[
|
|
540
|
+
"an image in a table cell",
|
|
541
|
+
"| a | b |\n|---|---|\n|  | c |",
|
|
542
|
+
],
|
|
543
|
+
[
|
|
544
|
+
"an image inside a link (a badge)",
|
|
545
|
+
"[](https://ci.example)",
|
|
546
|
+
],
|
|
547
|
+
[
|
|
548
|
+
"an image right after an exclamation mark",
|
|
549
|
+
"Wow!",
|
|
550
|
+
],
|
|
551
|
+
[
|
|
552
|
+
"an image after an escaped backslash",
|
|
553
|
+
"\\\\",
|
|
554
|
+
],
|
|
555
|
+
["an image after a code span", "`code`"],
|
|
556
|
+
[
|
|
557
|
+
"an image inside raw inline HTML",
|
|
558
|
+
"<span></span>",
|
|
559
|
+
],
|
|
560
|
+
[
|
|
561
|
+
"two images in a row",
|
|
562
|
+
"",
|
|
563
|
+
],
|
|
564
|
+
[
|
|
565
|
+
"an image in the alt text of another",
|
|
566
|
+
"](https://t.example/2.png)",
|
|
567
|
+
],
|
|
568
|
+
[
|
|
569
|
+
"an image on a line with Windows line endings",
|
|
570
|
+
"a\r\n\r\nb",
|
|
571
|
+
],
|
|
572
|
+
[
|
|
573
|
+
"an image with tab-indented text before it",
|
|
574
|
+
"\tnot code\n\n",
|
|
575
|
+
],
|
|
576
|
+
])("leaves nothing to fetch in %s", (_label: string, text: string) => {
|
|
577
|
+
const before: number = images(text).length;
|
|
578
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
579
|
+
|
|
580
|
+
// Each case really does hold an image before it is neutralised.
|
|
581
|
+
expect(before).toBeGreaterThan(0);
|
|
582
|
+
expectNoOpenImage(result);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("keeps a reference image's definition, so the link still goes where the image did", () => {
|
|
586
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(
|
|
587
|
+
"See ![x][pic]\n\n[pic]: https://t.example/p.png",
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
expect(result).toBe("See \\![x][pic]\n\n[pic]: https://t.example/p.png");
|
|
591
|
+
expect(links(result)[0]!.href).toBe("https://t.example/p.png");
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test("changes only the image's own exclamation mark", () => {
|
|
595
|
+
expect(
|
|
596
|
+
neutralizeMarkdownImagesAndDiagrams("Wow!"),
|
|
597
|
+
).toBe("Wow!\\");
|
|
598
|
+
expect(
|
|
599
|
+
neutralizeMarkdownImagesAndDiagrams("\\\\"),
|
|
600
|
+
).toBe("\\\\\\");
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
/*
|
|
604
|
+
* An address may hold image syntax, and every renderer reads a backslash
|
|
605
|
+
* in an address as the character after it: the link goes where it did.
|
|
606
|
+
*/
|
|
607
|
+
test("an image's syntax in a link's address is broken, and the link still goes to the same address", () => {
|
|
608
|
+
const text: string = "[see](https://t.example/)";
|
|
609
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
610
|
+
|
|
611
|
+
expect(result).toBe("[see](https://t.example/\\)");
|
|
612
|
+
expectNoOpenImage(result);
|
|
613
|
+
expect(
|
|
614
|
+
links(result).map((link: Tokens.Link): string => {
|
|
615
|
+
return link.href;
|
|
616
|
+
}),
|
|
617
|
+
).toEqual(
|
|
618
|
+
links(text).map((link: Tokens.Link): string => {
|
|
619
|
+
return link.href;
|
|
620
|
+
}),
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test.each([
|
|
625
|
+
["an escaped image", "\\"],
|
|
626
|
+
[
|
|
627
|
+
"an exclamation mark before a link",
|
|
628
|
+
"Look! [the page](https://t.example)",
|
|
629
|
+
],
|
|
630
|
+
["a plain link", "[the page](https://t.example/p.png)"],
|
|
631
|
+
["an autolink", "<https://t.example/p.png>"],
|
|
632
|
+
["a bare address", "https://t.example/p.png"],
|
|
633
|
+
["text with no Markdown at all", "Checkout fails at step 3."],
|
|
634
|
+
["an empty text", ""],
|
|
635
|
+
// Nothing after them could complete an image.
|
|
636
|
+
["an exclamation mark before brackets", "Wow![sic] that failed"],
|
|
637
|
+
[
|
|
638
|
+
"Rust's vec! in a code block",
|
|
639
|
+
"```rust\nlet v = vec![1, 2, 3];\n```\n\nSee [the docs](https://docs.example)",
|
|
640
|
+
],
|
|
641
|
+
["a non-null assertion in a code span", "`map.get(key)![0]` is undefined"],
|
|
642
|
+
["image syntax that is never closed", "![".repeat(50)],
|
|
643
|
+
[
|
|
644
|
+
"brackets closed only after the paragraph ends",
|
|
645
|
+
"",
|
|
646
|
+
],
|
|
647
|
+
/*
|
|
648
|
+
* A "]:" that could not start a link reference definition: only one at
|
|
649
|
+
* the start of a line - after indentation, or block quote, list or
|
|
650
|
+
* footnote markers - could complete "![y]".
|
|
651
|
+
*/
|
|
652
|
+
[
|
|
653
|
+
"code beside a Python slice elsewhere in the text",
|
|
654
|
+
"`x = ![y]`\n\nfor x in lines[1:]:\n print(x)",
|
|
655
|
+
],
|
|
656
|
+
[
|
|
657
|
+
"code beside a computed key and a log line",
|
|
658
|
+
"`list![0]` then `{ [key]: value }`, and sshd[1]: accepted",
|
|
659
|
+
],
|
|
660
|
+
])("leaves %s exactly as written", (_label: string, text: string) => {
|
|
661
|
+
expect(neutralizeMarkdownImagesAndDiagrams(text)).toBe(text);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
/*
|
|
665
|
+
* A fence opened at the very start of a line after a blank line (or at the
|
|
666
|
+
* start of the text, or right after another such fence closes) is fenced
|
|
667
|
+
* code to every renderer here, so image syntax in it can never be an
|
|
668
|
+
* image: it is left byte for byte, and code copied out of a report is the
|
|
669
|
+
* code that was reported - Rust's vec! with indexing after it included,
|
|
670
|
+
* where "][" could otherwise complete the "\n```",
|
|
675
|
+
"~~~\n\n~~~",
|
|
676
|
+
"```rust\nlet grid = vec![vec![0; w]; h];\nlet v = grid[y][x];\n```",
|
|
677
|
+
"Steps:\n\n```ts\ninterface Map {\n [key: string]: T;\n}\nconst v = map![key];\n```\n\n[a]: https://docs.example",
|
|
678
|
+
"```\n\n```\n```\n\n```",
|
|
679
|
+
"````\n```\n\n```\n````",
|
|
680
|
+
"```\n<div>\n\n\n```",
|
|
681
|
+
];
|
|
682
|
+
|
|
683
|
+
for (const text of texts) {
|
|
684
|
+
expect(neutralizeMarkdownImagesAndDiagrams(text)).toBe(text);
|
|
685
|
+
expect(images(text)).toEqual([]);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
expectInertInDashboard(texts);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
/*
|
|
692
|
+
* Where the renderers could pair the fences differently - a fence that is
|
|
693
|
+
* indented, inside a container or after text, an HTML block, a closing
|
|
694
|
+
* fence only one of them accepts, a byte-order mark - no fence is trusted
|
|
695
|
+
* any more, and image syntax after it is broken as anywhere else.
|
|
696
|
+
*/
|
|
697
|
+
test.each([
|
|
698
|
+
[
|
|
699
|
+
"a fence in a list item, ended by a line that is not",
|
|
700
|
+
"- item\n\n ```\n code\n\n```",
|
|
701
|
+
],
|
|
702
|
+
[
|
|
703
|
+
"a fence right after raw HTML",
|
|
704
|
+
"<div>\n```\n\n\n```",
|
|
705
|
+
],
|
|
706
|
+
[
|
|
707
|
+
"a fence inside an HTML comment",
|
|
708
|
+
"<!--\n\n```\n-->\n\n```",
|
|
709
|
+
],
|
|
710
|
+
[
|
|
711
|
+
"a fence closed only as marked reads it",
|
|
712
|
+
"```\ncode\n```~\n\n```\n\n```",
|
|
713
|
+
],
|
|
714
|
+
[
|
|
715
|
+
"a fence closed only as CommonMark reads it",
|
|
716
|
+
"```\ncode\n```\t\n\n```\n\n```",
|
|
717
|
+
],
|
|
718
|
+
[
|
|
719
|
+
"a fence after a byte-order mark",
|
|
720
|
+
"\uFEFF```\n\n```\n\n```",
|
|
721
|
+
],
|
|
722
|
+
[
|
|
723
|
+
"an indented fence",
|
|
724
|
+
" ````\n\n```\n\n ````\n\n\n```",
|
|
725
|
+
],
|
|
726
|
+
[
|
|
727
|
+
"a fence after a paragraph line",
|
|
728
|
+
"text\n```\n\n```\n\n```",
|
|
729
|
+
],
|
|
730
|
+
])("breaks image syntax after %s", (_label: string, text: string) => {
|
|
731
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
732
|
+
|
|
733
|
+
expectNoOpenImage(result);
|
|
734
|
+
expectInertInDashboard([result]);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("keeps the line endings of a text it leaves alone", () => {
|
|
738
|
+
const text: string = "a\r\nb `![x]`\r\n";
|
|
739
|
+
|
|
740
|
+
expect(neutralizeMarkdownImagesAndDiagrams(text)).toBe(text);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
/*
|
|
744
|
+
* Code shows its characters as they are, so a backslash there would show.
|
|
745
|
+
* Image syntax in other code that something could complete gets an
|
|
746
|
+
* invisible word joiner between its "!" and "[" instead: the code reads
|
|
747
|
+
* as typed, and no renderer that reads it as anything but code finds an
|
|
748
|
+
* image in it either.
|
|
749
|
+
*/
|
|
750
|
+
test.each([
|
|
751
|
+
["a code span", "Use `` to embed"],
|
|
752
|
+
[
|
|
753
|
+
"a code span of two backticks",
|
|
754
|
+
"Use ``a ` `` here",
|
|
755
|
+
],
|
|
756
|
+
[
|
|
757
|
+
"a fenced code block right after a line of text",
|
|
758
|
+
"Run:\n```markdown\n\n```",
|
|
759
|
+
],
|
|
760
|
+
["an indented code block", "Text:\n\n "],
|
|
761
|
+
[
|
|
762
|
+
"a code block inside a list",
|
|
763
|
+
"- item\n\n ```\n \n ```",
|
|
764
|
+
],
|
|
765
|
+
[
|
|
766
|
+
"a code block inside a block quote",
|
|
767
|
+
"> ```\n> \n> ```",
|
|
768
|
+
],
|
|
769
|
+
["a raw HTML block", "<div>\n\n</div>"],
|
|
770
|
+
])(
|
|
771
|
+
"breaks image syntax in %s invisibly, so it reads as written",
|
|
772
|
+
(_label: string, text: string) => {
|
|
773
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
774
|
+
|
|
775
|
+
expect(result).not.toBe(text);
|
|
776
|
+
expect(withoutJoiners(result)).toBe(text);
|
|
777
|
+
expect(result).toContain(`!${WORD_JOINER}[`);
|
|
778
|
+
expectNoOpenImage(result);
|
|
779
|
+
expect(codeSpans(result).map(withoutJoiners)).toEqual(codeSpans(text));
|
|
780
|
+
expect(
|
|
781
|
+
codeBlocks(result).map((block: Tokens.Code): string => {
|
|
782
|
+
return withoutJoiners(block.text);
|
|
783
|
+
}),
|
|
784
|
+
).toEqual(
|
|
785
|
+
codeBlocks(text).map((block: Tokens.Code): string => {
|
|
786
|
+
return block.text;
|
|
787
|
+
}),
|
|
788
|
+
);
|
|
789
|
+
},
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
test("code beside an image reads as written: only the image becomes a link", () => {
|
|
793
|
+
const text: string =
|
|
794
|
+
"`` \n\n```\n\n```";
|
|
795
|
+
|
|
796
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
797
|
+
|
|
798
|
+
expectInert(result);
|
|
799
|
+
expectInertInDashboard([result]);
|
|
800
|
+
expect(codeSpans(result).map(withoutJoiners)).toEqual(codeSpans(text));
|
|
801
|
+
expect(
|
|
802
|
+
codeBlocks(result).map((block: Tokens.Code): string => {
|
|
803
|
+
return block.text;
|
|
804
|
+
}),
|
|
805
|
+
).toEqual([""]);
|
|
806
|
+
expect(result).toBe(
|
|
807
|
+
`\`!${WORD_JOINER}[kept](https://t.example/k.png)\` \\\n\n\`\`\`\n\n\`\`\``,
|
|
808
|
+
);
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
test("breaks an image in another's alt text too, in one pass, without touching the code beside them", () => {
|
|
812
|
+
const text: string =
|
|
813
|
+
"`` ](https://t.example/2.png)";
|
|
814
|
+
|
|
815
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
816
|
+
|
|
817
|
+
expectNoOpenImage(result);
|
|
818
|
+
expect(codeSpans(result).map(withoutJoiners)).toEqual([
|
|
819
|
+
"",
|
|
820
|
+
]);
|
|
821
|
+
expect(result).toBe(
|
|
822
|
+
`\`!${WORD_JOINER}[kept](https://t.example/k.png)\` \\](https://t.example/2.png)`,
|
|
823
|
+
);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
/*
|
|
827
|
+
* The dashboard reads incident text with remark and GitHub's extensions,
|
|
828
|
+
* footnotes included; the emails with marked. They disagree on these
|
|
829
|
+
* shapes: to marked the image is in an indented code block, or in what
|
|
830
|
+
* it takes for a link definition, while the dashboard shows it as an
|
|
831
|
+
* image - which is why no reading of the text is trusted to decide
|
|
832
|
+
* whether an image is safe to leave. (Checked against the dashboard's
|
|
833
|
+
* react-markdown and remark-gfm, which jest stubs here.)
|
|
834
|
+
*/
|
|
835
|
+
test.each([
|
|
836
|
+
[
|
|
837
|
+
"a link definition followed by an indented line",
|
|
838
|
+
"[a]: https://x.example\n ",
|
|
839
|
+
`[a]: https://x.example\n !${WORD_JOINER}[x](https://t.example/p.png)`,
|
|
840
|
+
],
|
|
841
|
+
[
|
|
842
|
+
"a footnote definition",
|
|
843
|
+
"See[^1]\n\n[^1]: ",
|
|
844
|
+
"See[^1]\n\n[^1]: \\",
|
|
845
|
+
],
|
|
846
|
+
[
|
|
847
|
+
"a footnote's indented paragraph",
|
|
848
|
+
"See[^1]\n\n[^1]: note\n\n ",
|
|
849
|
+
`See[^1]\n\n[^1]: note\n\n !${WORD_JOINER}[x](https://t.example/p.png)`,
|
|
850
|
+
],
|
|
851
|
+
[
|
|
852
|
+
"a footnote in a list item",
|
|
853
|
+
"1. a\n\n [^n]: \n\nsee[^n]",
|
|
854
|
+
"1. a\n\n [^n]: \\\n\nsee[^n]",
|
|
855
|
+
],
|
|
856
|
+
[
|
|
857
|
+
"a reference image defined inside a footnote",
|
|
858
|
+
"See[^1]\n\n[^1]: [pic]: https://t.example/p.png\n\n![pic]",
|
|
859
|
+
"See[^1]\n\n[^1]: [pic]: https://t.example/p.png\n\n\\![pic]",
|
|
860
|
+
],
|
|
861
|
+
[
|
|
862
|
+
"a reference image defined after a byte order mark",
|
|
863
|
+
"\uFEFF[pic]: https://t.example/p.png\n\n![pic]",
|
|
864
|
+
"\uFEFF[pic]: https://t.example/p.png\n\n\\![pic]",
|
|
865
|
+
],
|
|
866
|
+
])(
|
|
867
|
+
"leaves no image the dashboard would show in %s",
|
|
868
|
+
(_label: string, text: string, expected: string) => {
|
|
869
|
+
// marked finds no image here - the dashboard's renderer does.
|
|
870
|
+
expect(images(text)).toEqual([]);
|
|
871
|
+
|
|
872
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
873
|
+
|
|
874
|
+
expect(result).toBe(expected);
|
|
875
|
+
expectNoOpenImage(result);
|
|
876
|
+
},
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
/*
|
|
880
|
+
* The lexer only chooses how each place is changed; a reporter who types
|
|
881
|
+
* the private use characters the places are tagged with cannot make one
|
|
882
|
+
* of them be skipped.
|
|
883
|
+
*/
|
|
884
|
+
test("a reporter who types the tagging characters leaves nothing open", () => {
|
|
885
|
+
const text: string =
|
|
886
|
+
"\uE0000\uE001 `\uE0001\uE001`  \uE0002\uE001";
|
|
887
|
+
|
|
888
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
889
|
+
|
|
890
|
+
expectNoOpenImage(result);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
test("keeps every other character of the text", () => {
|
|
894
|
+
const text: string =
|
|
895
|
+
"# Checkout\n\nSteps:\n\n1. Open **cart**\n2. See  and [logs](https://logs.example)\n\n> quoted\n";
|
|
896
|
+
|
|
897
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
898
|
+
|
|
899
|
+
expect(result.replace("\\![shot]", "![shot]")).toBe(text);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
test.each([[null], [undefined]])(
|
|
903
|
+
"turns %p into an empty text",
|
|
904
|
+
(value: null | undefined) => {
|
|
905
|
+
expect(neutralizeMarkdownImagesAndDiagrams(value)).toBe("");
|
|
906
|
+
},
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
/*
|
|
910
|
+
* A description is capped at 20000 characters and a Rich text answer at
|
|
911
|
+
* 10000 - all of it image syntax, emphasis around it, or whatever else a
|
|
912
|
+
* stranger with the form's link chooses - and neutralising it runs in the
|
|
913
|
+
* process that serves every tenant. marked takes seconds on some of these
|
|
914
|
+
* shapes (the emphasis ones, and brackets), so it only reads a text of at
|
|
915
|
+
* most LEXED_TEXT_MAX_LENGTH characters; at every one of these lengths,
|
|
916
|
+
* neutralising stays a matter of milliseconds - the bound here is
|
|
917
|
+
* generous - and leaves nothing to fetch. (marked is not asked whether
|
|
918
|
+
* the result is inert: it would take seconds on some of them itself.)
|
|
919
|
+
*/
|
|
920
|
+
test.each(WORST_CASE_SHAPES)(
|
|
921
|
+
"neutralises a text made of %s quickly, at every length a form takes",
|
|
922
|
+
(_label: string, shape: (length: number) => string) => {
|
|
923
|
+
for (const length of [
|
|
924
|
+
INCIDENT_FORM_DESCRIPTION_MAX_LENGTH,
|
|
925
|
+
INCIDENT_FORM_CUSTOM_FIELD_TEXT_MAX_LENGTH,
|
|
926
|
+
LEXED_TEXT_MAX_LENGTH,
|
|
927
|
+
]) {
|
|
928
|
+
const text: string = shape(length);
|
|
929
|
+
|
|
930
|
+
const started: number = Date.now();
|
|
931
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
932
|
+
const elapsed: number = Date.now() - started;
|
|
933
|
+
|
|
934
|
+
expect({
|
|
935
|
+
length: text.length,
|
|
936
|
+
fast: elapsed < TIME_BOUND_IN_MS,
|
|
937
|
+
}).toEqual({ length: length, fast: true });
|
|
938
|
+
expect(openImageSyntax(result)).toEqual([]);
|
|
939
|
+
expect(withReferencesDecoded(result).toLowerCase()).not.toMatch(
|
|
940
|
+
/(`{3,}|~{3,})[ \t]*mermaid/,
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
if (length <= LEXED_TEXT_MAX_LENGTH) {
|
|
944
|
+
expectInert(result);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
test("hands the lexer a text of at most LEXED_TEXT_MAX_LENGTH characters, and never a longer one", () => {
|
|
951
|
+
const lex: ReturnType<typeof jest.spyOn> = jest.spyOn(
|
|
952
|
+
Lexer.prototype,
|
|
953
|
+
"lex",
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
neutralizeMarkdownImagesAndDiagrams(
|
|
957
|
+
repeatTo("See  ", LEXED_TEXT_MAX_LENGTH),
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
expect(lex).toHaveBeenCalledTimes(1);
|
|
961
|
+
|
|
962
|
+
lex.mockClear();
|
|
963
|
+
|
|
964
|
+
neutralizeMarkdownImagesAndDiagrams(
|
|
965
|
+
repeatTo("See  ", LEXED_TEXT_MAX_LENGTH + 1),
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
expect(lex).not.toHaveBeenCalled();
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
/*
|
|
972
|
+
* Without the lexer, what each place is has to be told from its own line.
|
|
973
|
+
* For the shapes a real report has, that gives exactly what the lexer
|
|
974
|
+
* gives: an image in text becomes a link, with its address as its text
|
|
975
|
+
* when it had none; image syntax in a code span stays as typed, joined
|
|
976
|
+
* invisibly; a mermaid fence every renderer reads as one is renamed.
|
|
977
|
+
*/
|
|
978
|
+
test.each([
|
|
979
|
+
["an image without alt text", "See  here."],
|
|
980
|
+
["an image with alt text", ""],
|
|
981
|
+
[
|
|
982
|
+
"an image with an address in angle brackets and a title",
|
|
983
|
+
'',
|
|
984
|
+
],
|
|
985
|
+
["an image with no address", "![]()"],
|
|
986
|
+
["image syntax in a code span", "Use ``."],
|
|
987
|
+
["an indented code block", "Text:\n\n "],
|
|
988
|
+
["a mermaid fence", "```mermaid\ngraph TD\n```"],
|
|
989
|
+
[
|
|
990
|
+
"a mermaid fence spelled with a reference",
|
|
991
|
+
"```mermaid\ngraph TD\n```",
|
|
992
|
+
],
|
|
993
|
+
["image syntax in fenced code", "```\n\n```"],
|
|
994
|
+
])(
|
|
995
|
+
"neutralises %s in a text too long for the lexer as in a short one",
|
|
996
|
+
(_label: string, text: string) => {
|
|
997
|
+
const opening: string = `${repeatTo("Every order fails at checkout. ", LEXED_TEXT_MAX_LENGTH)}\n\n`;
|
|
998
|
+
|
|
999
|
+
expect(neutralizeMarkdownImagesAndDiagrams(opening + text)).toBe(
|
|
1000
|
+
opening + neutralizeMarkdownImagesAndDiagrams(text),
|
|
1001
|
+
);
|
|
1002
|
+
},
|
|
1003
|
+
);
|
|
1004
|
+
|
|
1005
|
+
test.each([
|
|
1006
|
+
[
|
|
1007
|
+
"a mermaid fence in a block quote",
|
|
1008
|
+
"> ```mermaid\n> graph TD\n> ```",
|
|
1009
|
+
`> \`\`\`${WORD_JOINER}mermaid\n> graph TD\n> \`\`\``,
|
|
1010
|
+
],
|
|
1011
|
+
[
|
|
1012
|
+
"image syntax in a code span that goes on to another line",
|
|
1013
|
+
"Use `a\n` here",
|
|
1014
|
+
"Use `a\n\\` here",
|
|
1015
|
+
],
|
|
1016
|
+
])(
|
|
1017
|
+
"breaks %s in a text too long for the lexer, if less tidily",
|
|
1018
|
+
(_label: string, text: string, expected: string) => {
|
|
1019
|
+
const opening: string = `${repeatTo("Every order fails at checkout. ", LEXED_TEXT_MAX_LENGTH)}\n\n`;
|
|
1020
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(
|
|
1021
|
+
opening + text,
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
expect(result).toBe(opening + expected);
|
|
1025
|
+
expectInert(result);
|
|
1026
|
+
expectInertInDashboard([result]);
|
|
1027
|
+
},
|
|
1028
|
+
);
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
describe("neutralizeMarkdownImagesAndDiagrams - mermaid diagrams", () => {
|
|
1032
|
+
test("shows a mermaid diagram as the code it is", () => {
|
|
1033
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(
|
|
1034
|
+
"Flow:\n\n```mermaid\ngraph TD\n A-->B\n```",
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
expect(result).toBe("Flow:\n\n```text\ngraph TD\n A-->B\n```");
|
|
1038
|
+
expectInert(result);
|
|
1039
|
+
expect(codeBlocks(result)[0]!.text).toBe("graph TD\n A-->B");
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test.each([
|
|
1043
|
+
["a tilde fence", "~~~mermaid\ngraph TD\n~~~"],
|
|
1044
|
+
["a longer fence", "````mermaid\ngraph TD\n````"],
|
|
1045
|
+
["a space before the language", "``` mermaid\ngraph TD\n```"],
|
|
1046
|
+
["more after the language", "```mermaid {init: {}}\ngraph TD\n```"],
|
|
1047
|
+
["another spelling", "```Mermaid\ngraph TD\n```"],
|
|
1048
|
+
["a language that starts with mermaid", "```mermaid-js\ngraph TD\n```"],
|
|
1049
|
+
["a fence in a block quote", "> ```mermaid\n> graph TD\n> ```"],
|
|
1050
|
+
["a fence in a list item", "- flow:\n\n ```mermaid\n graph TD\n ```"],
|
|
1051
|
+
["an indented fence", " ```mermaid\n graph TD\n ```"],
|
|
1052
|
+
])("demotes %s", (_label: string, text: string) => {
|
|
1053
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
1054
|
+
|
|
1055
|
+
expectInert(result);
|
|
1056
|
+
expect(codeBlocks(result)).toHaveLength(1);
|
|
1057
|
+
expect(result).not.toContain(WORD_JOINER);
|
|
1058
|
+
expect(result.toLowerCase()).toContain("text");
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
/*
|
|
1062
|
+
* Where the lexer finds no mermaid fence, "mermaid" after a fence run
|
|
1063
|
+
* still gets an invisible word joiner before it: whatever another
|
|
1064
|
+
* renderer makes of the text, no language there reads "mermaid", and
|
|
1065
|
+
* the text reads as typed.
|
|
1066
|
+
*/
|
|
1067
|
+
test.each([
|
|
1068
|
+
["an inline code span", "Use ```mermaid``` fences to draw"],
|
|
1069
|
+
[
|
|
1070
|
+
"a mermaid fence shown inside another code block",
|
|
1071
|
+
"````markdown\n```mermaid\ngraph TD\n```\n````",
|
|
1072
|
+
],
|
|
1073
|
+
["a fence indented as code", "Text:\n\n ```mermaid\n graph\n ```"],
|
|
1074
|
+
[
|
|
1075
|
+
"a fence in a footnote's indented paragraph (the dashboard draws it)",
|
|
1076
|
+
"See[^d]\n\n[^d]: flow\n\n ```mermaid\n graph TD\n ```",
|
|
1077
|
+
],
|
|
1078
|
+
[
|
|
1079
|
+
"a fence after a link definition, on an indented line",
|
|
1080
|
+
"[a]: https://x.example\n ```mermaid\n graph TD\n ```",
|
|
1081
|
+
],
|
|
1082
|
+
])(
|
|
1083
|
+
"breaks %s invisibly, so it reads as written",
|
|
1084
|
+
(_label: string, text: string) => {
|
|
1085
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
1086
|
+
|
|
1087
|
+
expectInert(result);
|
|
1088
|
+
expect(result).toContain(`${WORD_JOINER}mermaid`);
|
|
1089
|
+
expect(withoutJoiners(result)).toBe(text);
|
|
1090
|
+
},
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
test.each([
|
|
1094
|
+
["another language", "```js\nconst mermaid = 1;\n```"],
|
|
1095
|
+
["the word in text", "We draw mermaid diagrams."],
|
|
1096
|
+
])("leaves %s exactly as written", (_label: string, text: string) => {
|
|
1097
|
+
expect(neutralizeMarkdownImagesAndDiagrams(text)).toBe(text);
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
/*
|
|
1101
|
+
* The dashboard reads a fence's info string as CommonMark does, character
|
|
1102
|
+
* references decoded, so ```mermaid is a mermaid fence there - and a
|
|
1103
|
+
* viewer matching "language-mermaid" anywhere in a class would take
|
|
1104
|
+
* ```-language-mermaid for one too. So every "mermaid" in an info string,
|
|
1105
|
+
* as the dashboard decodes it, is broken, wherever it stands.
|
|
1106
|
+
*/
|
|
1107
|
+
const SPELLED_MERMAID_FENCES: Array<[string, string]> = [
|
|
1108
|
+
["a decimal reference", "```mermaid\ngraph TD\n```"],
|
|
1109
|
+
["a reference inside the word", "```mermaid\ngraph TD\n```"],
|
|
1110
|
+
["a hexadecimal reference", "```mermaid\ngraph TD\n```"],
|
|
1111
|
+
["an upper-case hexadecimal reference", "```mermaid\ngraph TD\n```"],
|
|
1112
|
+
["a reference with leading zeros", "```mermaid\ngraph TD\n```"],
|
|
1113
|
+
[
|
|
1114
|
+
"a reference for every letter",
|
|
1115
|
+
"```mermaid\ngraph TD\n```",
|
|
1116
|
+
],
|
|
1117
|
+
["a reference in a tilde fence", "~~~mermaid\ngraph TD\n~~~"],
|
|
1118
|
+
["spaces before a reference", "``` mermaid\ngraph TD\n```"],
|
|
1119
|
+
["a reference in a block quote", "> ```mermaid\n> graph TD\n> ```"],
|
|
1120
|
+
[
|
|
1121
|
+
"a reference in a list item",
|
|
1122
|
+
"- flow:\n\n ```mermaid\n graph TD\n ```",
|
|
1123
|
+
],
|
|
1124
|
+
[
|
|
1125
|
+
"a language ending in -language-mermaid",
|
|
1126
|
+
"```-language-mermaid\ngraph TD\n```",
|
|
1127
|
+
],
|
|
1128
|
+
[
|
|
1129
|
+
"a language ending in .language-mermaid",
|
|
1130
|
+
"```.language-mermaid\ngraph TD\n```",
|
|
1131
|
+
],
|
|
1132
|
+
[
|
|
1133
|
+
"a language ending in +language-mermaid, in a list item",
|
|
1134
|
+
"- ```+language-mermaid\n graph TD\n ```",
|
|
1135
|
+
],
|
|
1136
|
+
["capital letters", "```MERMAID\ngraph TD\n```"],
|
|
1137
|
+
];
|
|
1138
|
+
|
|
1139
|
+
test.each(SPELLED_MERMAID_FENCES)(
|
|
1140
|
+
"breaks a mermaid fence spelled with %s",
|
|
1141
|
+
(_label: string, text: string) => {
|
|
1142
|
+
const result: string = neutralizeMarkdownImagesAndDiagrams(text);
|
|
1143
|
+
|
|
1144
|
+
expect(result).not.toBe(text);
|
|
1145
|
+
expectInert(result);
|
|
1146
|
+
expect(neutralizeMarkdownImagesAndDiagrams(result)).toBe(result);
|
|
1147
|
+
},
|
|
1148
|
+
);
|
|
1149
|
+
|
|
1150
|
+
test("leaves the dashboard no mermaid fence where it would have found one in each", () => {
|
|
1151
|
+
const texts: Array<string> = SPELLED_MERMAID_FENCES.map(
|
|
1152
|
+
(fence: [string, string]): string => {
|
|
1153
|
+
return fence[1];
|
|
1154
|
+
},
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
// Every one of them really is a mermaid fence to the dashboard.
|
|
1158
|
+
for (const html of renderAsDashboard(texts)) {
|
|
1159
|
+
expect(html).toMatch(/class="language-[^"]*mermaid/i);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
expectInertInDashboard(texts.map(neutralizeMarkdownImagesAndDiagrams));
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
test.each([
|
|
1166
|
+
["```mermaid\ngraph TD\n```", "```text\ngraph TD\n```"],
|
|
1167
|
+
["```-language-mermaid\ngraph TD\n```", "```text\ngraph TD\n```"],
|
|
1168
|
+
["> ```mermaid\n> graph TD\n> ```", "> ```text\n> graph TD\n> ```"],
|
|
1169
|
+
[
|
|
1170
|
+
"```js mermaid\ngraph TD\n```",
|
|
1171
|
+
`\`\`\`js ${WORD_JOINER}mermaid\ngraph TD\n\`\`\``,
|
|
1172
|
+
],
|
|
1173
|
+
[
|
|
1174
|
+
"Use ```mermaid``` fences",
|
|
1175
|
+
`Use \`\`\`${WORD_JOINER}mermaid\`\`\` fences`,
|
|
1176
|
+
],
|
|
1177
|
+
])(
|
|
1178
|
+
"gives %j the language text where it is a fence, and breaks it elsewhere",
|
|
1179
|
+
(text: string, expected: string) => {
|
|
1180
|
+
expect(neutralizeMarkdownImagesAndDiagrams(text)).toBe(expected);
|
|
1181
|
+
},
|
|
1182
|
+
);
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
describe("neutralizeUntrustedPlainText", () => {
|
|
1186
|
+
test.each([
|
|
1187
|
+
["an image", " Checkout is down"],
|
|
1188
|
+
["a reference image", "![shot][r] Checkout is down"],
|
|
1189
|
+
[
|
|
1190
|
+
"an image in a link",
|
|
1191
|
+
"[](https://ci.example)",
|
|
1192
|
+
],
|
|
1193
|
+
["a mention", "<!channel> Checkout is down"],
|
|
1194
|
+
["a mermaid fence", "```mermaid Checkout is down"],
|
|
1195
|
+
["a mermaid fence spelled with a reference", "~~~mermaid down"],
|
|
1196
|
+
])(
|
|
1197
|
+
"breaks %s, and it reads exactly as typed",
|
|
1198
|
+
(_label: string, text: string) => {
|
|
1199
|
+
const result: string = neutralizeUntrustedPlainText(text);
|
|
1200
|
+
|
|
1201
|
+
expect(result).not.toBe(text);
|
|
1202
|
+
expect(withoutJoiners(result)).toBe(text);
|
|
1203
|
+
expect(result).not.toMatch(/!\[|<[!@#]/);
|
|
1204
|
+
expect(withReferencesDecoded(result).toLowerCase()).not.toMatch(
|
|
1205
|
+
/(`{3,}|~{3,})[ \t]*mermaid/,
|
|
1206
|
+
);
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
|
|
1210
|
+
/*
|
|
1211
|
+
* An incident's title is placed into Markdown as it is by much of
|
|
1212
|
+
* OneUptime: an episode takes up to 50 characters of it as its title,
|
|
1213
|
+
* episode feed items quote it, and a note template can hold it on a line
|
|
1214
|
+
* of its own. None of those finds an image or a diagram in it.
|
|
1215
|
+
*/
|
|
1216
|
+
test("leaves nothing that acts on its own wherever the title is placed", () => {
|
|
1217
|
+
const title: string = neutralizeUntrustedPlainText(
|
|
1218
|
+
"<!channel>  Checkout is down",
|
|
1219
|
+
);
|
|
1220
|
+
const fence: string = neutralizeUntrustedPlainText("```mermaid");
|
|
1221
|
+
|
|
1222
|
+
const placed: Array<string> = [
|
|
1223
|
+
`#### Episode #1 Created\n\n**${title.substring(0, 50)}**`,
|
|
1224
|
+
`**Incident INC-42** added to episode: ${title}`,
|
|
1225
|
+
`Added to **Episode #1**: ${title.substring(0, 50)}`,
|
|
1226
|
+
`${fence}\nflowchart TD\n A@{ img: "https://tracker.example/q.png" }\n\`\`\``,
|
|
1227
|
+
];
|
|
1228
|
+
|
|
1229
|
+
for (const markdown of placed) {
|
|
1230
|
+
expectInert(markdown);
|
|
1231
|
+
expect(slackText(markdown)).not.toMatch(/<(![a-z]|@[A-Z0-9]|#C)/);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
expectInertInDashboard(placed);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
test.each([
|
|
1238
|
+
["Checkout is down"],
|
|
1239
|
+
["Site 03 - payments (EU)"],
|
|
1240
|
+
["Wow! [sic]"],
|
|
1241
|
+
["a ! [b]"],
|
|
1242
|
+
["Hi!"],
|
|
1243
|
+
["<!-- not a mention -->"],
|
|
1244
|
+
["We draw mermaid diagrams."],
|
|
1245
|
+
[""],
|
|
1246
|
+
])("leaves %j exactly as it is", (text: string) => {
|
|
1247
|
+
expect(neutralizeUntrustedPlainText(text)).toBe(text);
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
/*
|
|
1251
|
+
* Beside other Markdown - an episode template puts the title and the
|
|
1252
|
+
* description side by side - a title must not define the reference an
|
|
1253
|
+
* image in the description reads, complete one opened there, or be a link.
|
|
1254
|
+
*/
|
|
1255
|
+
test.each([
|
|
1256
|
+
["a link reference definition", "[a]: https://tracker.example/p.png"],
|
|
1257
|
+
["a reference that completes an image", "[x][a] Checkout is down"],
|
|
1258
|
+
["a link", "[Verify your SSO](https://evil.example/login)"],
|
|
1259
|
+
])(
|
|
1260
|
+
"breaks %s in a title, and it reads exactly as typed",
|
|
1261
|
+
(_label: string, text: string) => {
|
|
1262
|
+
const result: string = neutralizeUntrustedPlainText(text);
|
|
1263
|
+
|
|
1264
|
+
expect(result).not.toBe(text);
|
|
1265
|
+
expect(withoutJoiners(result)).toBe(text);
|
|
1266
|
+
expect(result).not.toMatch(/\][([:]/);
|
|
1267
|
+
expect(neutralizeUntrustedPlainText(result)).toBe(result);
|
|
1268
|
+
},
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
test("a title placed beside the description by an episode template completes no image", () => {
|
|
1272
|
+
const title: string = neutralizeUntrustedPlainText(
|
|
1273
|
+
"[a]: https://tracker.example/p.png",
|
|
1274
|
+
);
|
|
1275
|
+
const description: string = neutralizeUntrustedMarkdown("![a]");
|
|
1276
|
+
// As IncidentGroupingEngineService fills an episode description template.
|
|
1277
|
+
const episodeDescription: string =
|
|
1278
|
+
"{{incidentTitle}}\n\n{{incidentDescription}}"
|
|
1279
|
+
.replace(/\{\{incidentTitle\}\}/g, (): string => {
|
|
1280
|
+
return title;
|
|
1281
|
+
})
|
|
1282
|
+
.replace(/\{\{incidentDescription\}\}/g, (): string => {
|
|
1283
|
+
return description;
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
expectInert(episodeDescription);
|
|
1287
|
+
expectInertInDashboard([episodeDescription]);
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
test("is idempotent", () => {
|
|
1291
|
+
const once: string = neutralizeUntrustedPlainText(
|
|
1292
|
+
"<!here>  ```mermaid",
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
expect(neutralizeUntrustedPlainText(once)).toBe(once);
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
test.each([[null], [undefined]])(
|
|
1299
|
+
"turns %p into an empty text",
|
|
1300
|
+
(value: null | undefined) => {
|
|
1301
|
+
expect(neutralizeUntrustedPlainText(value)).toBe("");
|
|
1302
|
+
},
|
|
1303
|
+
);
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
describe("neutralizeUntrustedMarkdown", () => {
|
|
1307
|
+
test("neutralises images, diagrams and mentions together", () => {
|
|
1308
|
+
const text: string =
|
|
1309
|
+
"<!here> \n\n```mermaid\ngraph TD\n```\n\n[Verify your session](https://evil.example/login) <@U0123ABC>";
|
|
1310
|
+
|
|
1311
|
+
const result: string = neutralizeUntrustedMarkdown(text);
|
|
1312
|
+
|
|
1313
|
+
expectNoOpenImage(result);
|
|
1314
|
+
expect(result).not.toMatch(/<[!@#]/);
|
|
1315
|
+
// Links stay links: the reporter linking the broken page is the point.
|
|
1316
|
+
expect(
|
|
1317
|
+
links(result).map((link: Tokens.Link): string => {
|
|
1318
|
+
return link.href;
|
|
1319
|
+
}),
|
|
1320
|
+
).toEqual(["https://t.example/p.png", "https://evil.example/login"]);
|
|
1321
|
+
expect(result.split(WORD_JOINER).join("")).toBe(
|
|
1322
|
+
"<!here> \\\n\n```text\ngraph TD\n```\n\n[Verify your session](https://evil.example/login) <@U0123ABC>",
|
|
1323
|
+
);
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
test("leaves ordinary Markdown exactly as written", () => {
|
|
1327
|
+
const text: string =
|
|
1328
|
+
"Checkout fails at **step 3**.\n\n- since 09:00\n- EU only\n\n```\nError 502\n```";
|
|
1329
|
+
|
|
1330
|
+
expect(neutralizeUntrustedMarkdown(text)).toBe(text);
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
test.each([[null], [undefined]])(
|
|
1334
|
+
"turns %p into an empty text",
|
|
1335
|
+
(value: null | undefined) => {
|
|
1336
|
+
expect(neutralizeUntrustedMarkdown(value)).toBe("");
|
|
1337
|
+
},
|
|
1338
|
+
);
|
|
1339
|
+
});
|