@oneuptime/common 14.0.8 → 14.0.10
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/AutoRemediationSuggestion.ts +60 -0
- package/Models/DatabaseModels/CephCluster.ts +213 -0
- package/Models/DatabaseModels/DatabaseServer.ts +213 -0
- package/Models/DatabaseModels/DockerHost.ts +213 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
- package/Models/DatabaseModels/GlobalConfig.ts +1 -1
- package/Models/DatabaseModels/Host.ts +213 -0
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
- package/Models/DatabaseModels/PodmanHost.ts +213 -0
- package/Models/DatabaseModels/Project.ts +55 -5
- package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
- package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
- package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
- package/Models/DatabaseModels/RunnerJob.ts +219 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
- package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
- package/Server/API/AIChatAPI.ts +6 -3
- package/Server/API/AIInvestigationAPI.ts +17 -10
- package/Server/API/AutoRemediationAPI.ts +598 -25
- package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- package/Server/API/ResourceAiAccessAPI.ts +1669 -0
- package/Server/Infrastructure/GlobalCache.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Semaphore.ts +22 -0
- package/Server/Middleware/TelemetryIngest.ts +51 -18
- package/Server/Services/AIService.ts +70 -1
- package/Server/Services/AlertService.ts +352 -11
- package/Server/Services/AlertSeverityService.ts +65 -15
- package/Server/Services/AnalyticsDatabaseService.ts +45 -1
- package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
- package/Server/Services/AutoRemediationRuleService.ts +1 -1
- package/Server/Services/CephClusterService.ts +109 -1
- package/Server/Services/DatabaseServerService.ts +116 -1
- package/Server/Services/DockerHostService.ts +109 -1
- package/Server/Services/DockerSwarmClusterService.ts +113 -1
- package/Server/Services/HostService.ts +126 -4
- package/Server/Services/IncidentService.ts +280 -104
- package/Server/Services/IncidentSeverityService.ts +66 -15
- package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
- package/Server/Services/KubernetesAiAgentService.ts +1634 -0
- package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
- package/Server/Services/KubernetesClusterService.ts +129 -21
- package/Server/Services/MetricRecordingRuleService.ts +47 -0
- package/Server/Services/PodmanHostService.ts +109 -1
- package/Server/Services/ProjectService.ts +32 -0
- package/Server/Services/ProxmoxClusterService.ts +110 -1
- package/Server/Services/ProxmoxResourceService.ts +322 -5
- package/Server/Services/ResourceAiAccessService.ts +1439 -0
- package/Server/Services/ResourceAiAgentJobService.ts +202 -0
- package/Server/Services/ResourceAiAgentService.ts +2432 -0
- package/Server/Services/RunnerJobService.ts +852 -108
- package/Server/Services/RunnerService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +737 -1
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
- package/Server/Services/TeamComplianceSettingService.ts +1125 -9
- package/Server/Services/TelemetryUsageBillingService.ts +28 -0
- package/Server/Services/TraceRecordingRuleService.ts +33 -0
- package/Server/Services/VMwareVCenterService.ts +110 -1
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1475 -51
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1000 -43
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
- package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
- package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +120 -7
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
- package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
- package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +402 -14
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
- package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
- package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
- package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
- package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +1073 -213
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
- package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
- package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
- package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
- package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
- package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
- package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
- package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
- package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
- package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
- package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
- package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
- package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
- package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
- package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
- package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +184 -19
- package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
- package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
- package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
- package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
- package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
- package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
- package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
- package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
- package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
- package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
- package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
- package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
- package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
- package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
- package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
- package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
- package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
- package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
- package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
- package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
- package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
- package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
- package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
- package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
- package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
- package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
- package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
- package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
- package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
- package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
- package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
- package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
- package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
- package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
- package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
- package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
- package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
- package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
- package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
- package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
- package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
- package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
- package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
- package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
- package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
- package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
- package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
- package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
- package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
- package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
- package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
- package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
- package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
- package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
- package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
- package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
- package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
- package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
- package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
- package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
- package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
- package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
- package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
- package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
- package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
- package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
- package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
- package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
- package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
- package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
- package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
- package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
- package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
- package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
- package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
- package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
- package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
- package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
- package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
- package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
- package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
- package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
- package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
- package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
- package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
- package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
- package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
- package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
- package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
- package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
- package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
- package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
- package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
- package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
- package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
- package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
- package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
- package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
- package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
- package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
- package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
- package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
- package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
- package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
- package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
- package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
- package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
- package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
- package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
- package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
- package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
- package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
- package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +119 -4
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +196 -2
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +123 -2
- package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
- package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
- package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
- package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
- package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
- package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
- package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
- package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
- package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
- package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
- package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
- package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
- package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
- package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
- package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
- package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
- package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
- package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
- package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
- package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
- package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
- package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
- package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
- package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
- package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
- package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +153 -15
- package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
- package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
- package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
- package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
- package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
- package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
- package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
- package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
- package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
- package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
- package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
- package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
- package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
- package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
- package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
- package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
- package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +75 -3
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
- package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
- package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
- package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
- package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
- package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
- package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
- package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
- package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
- package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
- package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
- package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
- package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
- package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
- package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
- package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
- package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
- package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
- package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
- package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
- package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
- package/Tests/UI/Components/Input.test.tsx +80 -1
- package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
- package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
- package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
- package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
- package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
- package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
- package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
- package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
- package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
- package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
- package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
- package/Tests/UI/Components/Toggle.test.tsx +66 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
- package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
- package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
- package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
- package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
- package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
- package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
- package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
- package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
- package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
- package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
- package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
- package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
- package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
- package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
- package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
- package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
- package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
- package/Types/AI/AIRunType.ts +9 -4
- package/Types/AI/InvestigationNotStartedReason.ts +6 -0
- package/Types/AI/ResourceAiAccessApi.ts +140 -0
- package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +197 -18
- package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
- package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
- package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
- package/Types/Monitor/RumAlertTemplates.ts +351 -4
- package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
- package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
- package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
- package/Types/Runbook/RunbookStepType.ts +29 -0
- package/Types/Team/ComplianceNotificationChannel.ts +22 -0
- package/Types/Team/ComplianceRule.ts +434 -0
- package/Types/Team/ComplianceRuleType.ts +21 -0
- package/Types/Team/TeamComplianceStatus.ts +81 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +40 -0
- package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
- package/UI/Components/ActionButton/RowActions.tsx +238 -0
- package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
- package/UI/Components/Charts/Line/LineChart.tsx +32 -2
- package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
- package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
- package/UI/Components/Charts/Utils/XAxis.ts +75 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
- package/UI/Components/Forms/Fields/FormField.tsx +10 -0
- package/UI/Components/Input/Input.tsx +90 -57
- package/UI/Components/List/ListRow.tsx +8 -74
- package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
- package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
- package/UI/Components/OrderedStatesList/Item.tsx +7 -72
- package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
- package/UI/Components/Table/TableBody.tsx +8 -2
- package/UI/Components/Table/TableRow.tsx +20 -140
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
- package/UI/Components/TelemetryViewer/types.ts +2 -0
- package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
- package/UI/Components/Toggle/Toggle.tsx +26 -0
- package/UI/Components/Tooltip/Tooltip.tsx +8 -1
- package/UI/Styles/Theme.css +14 -2
- package/Utils/AI/InvestigationReport.ts +121 -15
- package/Utils/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
- package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
- package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
- package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
- package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
- package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
- package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
- package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
- package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
- package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
- package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
- package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
- package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +219 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +56 -5
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
- package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js +219 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +6 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +374 -24
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
- package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
- package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
- package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +41 -19
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +59 -1
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +248 -13
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +51 -15
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +104 -0
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerService.js +99 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +104 -0
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +114 -2
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +199 -72
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
- package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +104 -0
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
- package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
- package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
- package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
- package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/RunnerJobService.js +522 -97
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +1 -1
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
- package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
- package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
- package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1056 -43
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +683 -42
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +89 -9
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +294 -15
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +768 -152
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
- package/build/dist/Types/AI/AIRunType.js +9 -4
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
- package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
- package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
- package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +126 -45
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
- package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
- package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
- package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRule.js +304 -0
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
- package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
- package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
- package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +36 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
- package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +69 -54
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/List/ListRow.js +4 -39
- package/build/dist/UI/Components/List/ListRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
- package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +6 -2
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +12 -75
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/Utils/AI/InvestigationReport.js +71 -11
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
- package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
- package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
- package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
- package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
- package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
- package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
- package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1721 @@
|
|
|
1
|
+
jest.mock("../../../../Server/EnvironmentConfig", () => {
|
|
2
|
+
return {
|
|
3
|
+
...(jest.requireActual("../../../../Server/EnvironmentConfig") as Record<
|
|
4
|
+
string,
|
|
5
|
+
unknown
|
|
6
|
+
>),
|
|
7
|
+
MicrosoftTeamsAppClientId: "11111111-2222-3333-4444-555555555555",
|
|
8
|
+
MicrosoftTeamsAppClientSecret: "test-secret",
|
|
9
|
+
MicrosoftTeamsAppTenantId: "test-tenant",
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
jest.mock(
|
|
14
|
+
"../../../../Server/Infrastructure/Postgres/DataSourceOptions",
|
|
15
|
+
() => {
|
|
16
|
+
return {};
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* The repo-wide botbuilder manual mock does not expose TeamsInfo or
|
|
22
|
+
* MessageFactory.attachment, which chat capture and the welcome card use.
|
|
23
|
+
*/
|
|
24
|
+
jest.mock("botbuilder", () => {
|
|
25
|
+
return {
|
|
26
|
+
CloudAdapter: class CloudAdapter {},
|
|
27
|
+
ConfigurationBotFrameworkAuthentication: class ConfigurationBotFrameworkAuthentication {},
|
|
28
|
+
TeamsActivityHandler: class TeamsActivityHandler {},
|
|
29
|
+
TurnContext: class TurnContext {},
|
|
30
|
+
ActivityHandler: class ActivityHandler {},
|
|
31
|
+
MessageFactory: {
|
|
32
|
+
text: jest.fn(),
|
|
33
|
+
attachment: jest.fn((attachment: unknown) => {
|
|
34
|
+
return { type: "message", attachments: [attachment] };
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
CardFactory: { heroCard: jest.fn() },
|
|
38
|
+
TeamsInfo: {
|
|
39
|
+
getMembers: jest.fn(),
|
|
40
|
+
getPagedMembers: jest.fn(),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
import Entities from "../../../../Models/DatabaseModels/Index";
|
|
46
|
+
import {
|
|
47
|
+
MicrosoftTeamsChat,
|
|
48
|
+
MicrosoftTeamsMiscData,
|
|
49
|
+
} from "../../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
50
|
+
import PostgresAppInstance from "../../../../Server/Infrastructure/PostgresDatabase";
|
|
51
|
+
import MicrosoftTeamsUtil, {
|
|
52
|
+
MicrosoftTeamsChatNameRefreshResult,
|
|
53
|
+
MicrosoftTeamsChatNameUpdate,
|
|
54
|
+
} from "../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
|
|
55
|
+
import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
|
|
56
|
+
import HTTPResponse from "../../../../Types/API/HTTPResponse";
|
|
57
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
58
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
59
|
+
import WorkspaceType from "../../../../Types/Workspace/WorkspaceType";
|
|
60
|
+
import API, { APIRequestOptions } from "../../../../Utils/API";
|
|
61
|
+
import {
|
|
62
|
+
TeamsInfo,
|
|
63
|
+
type TeamsChannelAccount,
|
|
64
|
+
type TeamsPagedMembersResult,
|
|
65
|
+
type TurnContext,
|
|
66
|
+
} from "botbuilder";
|
|
67
|
+
import { DataSource, Logger } from "typeorm";
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Issue #4106 - Microsoft Teams group chats listed by their members instead
|
|
71
|
+
* of their Teams name - against a migrated Postgres.
|
|
72
|
+
*
|
|
73
|
+
* MicrosoftTeamsGroupChatNames.test.ts pins the logic with every
|
|
74
|
+
* WorkspaceProjectAuthToken read and write faked. What only a database can
|
|
75
|
+
* show is what is actually stored in the miscData jsonb column:
|
|
76
|
+
*
|
|
77
|
+
* - that capture stores the chat's Teams name (topic), only the first three
|
|
78
|
+
* member names and the member count - on EVERY connected project of the
|
|
79
|
+
* tenant and on no other tenant;
|
|
80
|
+
* - that a re-capture builds on the stored record: the connection date and a
|
|
81
|
+
* Teams name Graph will not (or cannot) read again are kept, and a roster
|
|
82
|
+
* that could not be read leaves the stored one - or the missing member
|
|
83
|
+
* count that makes the next message try again - as it was;
|
|
84
|
+
* - that a Refresh Chats rename writes the name and Teams name alone, onto
|
|
85
|
+
* the record as it is now (a chat removed meanwhile is not resurrected, one
|
|
86
|
+
* re-captured meanwhile keeps its new record), with one write per row and
|
|
87
|
+
* one Graph token for every lookup;
|
|
88
|
+
* - that a long Teams name cut where an emoji sits is stored at all: the cut
|
|
89
|
+
* used to leave half of the emoji, a lone UTF-16 surrogate, and Postgres
|
|
90
|
+
* refuses the whole jsonb write that carries one;
|
|
91
|
+
* - that non-ASCII names round-trip character for character, and that the
|
|
92
|
+
* message backfill's staleness check reads what capture wrote.
|
|
93
|
+
*
|
|
94
|
+
* Opt in with RUN_POSTGRES_MICROSOFT_TEAMS_CHAT_TESTS=true against a Postgres
|
|
95
|
+
* migrated to the current head - the Postgres Schema Drift workflow's
|
|
96
|
+
* database right after its drift check, e.g. from packages/Common:
|
|
97
|
+
*
|
|
98
|
+
* RUN_POSTGRES_MICROSOFT_TEAMS_CHAT_TESTS=true \
|
|
99
|
+
* MICROSOFT_TEAMS_CHAT_TEST_DATABASE_HOST=127.0.0.1 \
|
|
100
|
+
* MICROSOFT_TEAMS_CHAT_TEST_DATABASE_PORT=5400 \
|
|
101
|
+
* DATABASE_PASSWORD=... node node_modules/.bin/jest --runInBand \
|
|
102
|
+
* Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts --forceExit
|
|
103
|
+
*
|
|
104
|
+
* Without the flag the suite is skipped, so the Common test job - whose
|
|
105
|
+
* Postgres is not migrated - never runs it.
|
|
106
|
+
*
|
|
107
|
+
* The WorkspaceProjectAuthToken table's STRUCTURE is cloned (LIKE ...
|
|
108
|
+
* INCLUDING ALL) into a uniquely named schema (search_path holds that schema
|
|
109
|
+
* first) that is dropped afterwards; every row is synthetic.
|
|
110
|
+
*
|
|
111
|
+
* The production MicrosoftTeamsUtil and WorkspaceProjectAuthTokenService run
|
|
112
|
+
* unchanged - getValidAccessToken included, which hands out the Graph app
|
|
113
|
+
* token stored on the row. Only Microsoft is faked: Graph (API.get, and
|
|
114
|
+
* API.post so a token refresh would be loud) and the Bot Framework
|
|
115
|
+
* (TeamsInfo and the TurnContext). Every statement Postgres rejects is
|
|
116
|
+
* recorded through the DataSource's logger and fails the test, even when a
|
|
117
|
+
* caller catches it - chat capture swallows its own errors, so a refused
|
|
118
|
+
* write would otherwise pass as "nothing stored".
|
|
119
|
+
*/
|
|
120
|
+
const describePostgres: typeof describe =
|
|
121
|
+
process.env["RUN_POSTGRES_MICROSOFT_TEAMS_CHAT_TESTS"] === "true"
|
|
122
|
+
? describe
|
|
123
|
+
: describe.skip;
|
|
124
|
+
|
|
125
|
+
const TABLE: string = "WorkspaceProjectAuthToken";
|
|
126
|
+
|
|
127
|
+
const TENANT_ID: string = "6f1c2d3e-4b5a-4968-8778-a1b2c3d4e5f6";
|
|
128
|
+
const OTHER_TENANT_ID: string = "0a9b8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d";
|
|
129
|
+
|
|
130
|
+
const BOT_RECIPIENT_ID: string = "28:bot-recipient-id";
|
|
131
|
+
const SERVICE_URL: string = "https://smba.trafficmanager.net/amer/";
|
|
132
|
+
const MOVED_SERVICE_URL: string = "https://smba.trafficmanager.net/emea/";
|
|
133
|
+
|
|
134
|
+
// Captured before rosters (and names from Graph) were stored.
|
|
135
|
+
const LEGACY_GROUP_CHAT_ID: string =
|
|
136
|
+
"19:1a2b3c4d5e6f47a8b9c0d1e2f3a4b5c6@thread.v2";
|
|
137
|
+
/*
|
|
138
|
+
* Captured by the first release of the #4106 fix, which stored the whole
|
|
139
|
+
* roster's names and no member count.
|
|
140
|
+
*/
|
|
141
|
+
const FIRST_RELEASE_GROUP_CHAT_ID: string =
|
|
142
|
+
"19:7c6b5a4938271605f4e3d2c1b0a99887@thread.v2";
|
|
143
|
+
// Captured with its roster; listed by its members.
|
|
144
|
+
const ROSTER_GROUP_CHAT_ID: string =
|
|
145
|
+
"19:9f8e7d6c5b4a43928170f1e2d3c4b5a6@thread.v2";
|
|
146
|
+
// Captured under its Teams name.
|
|
147
|
+
const NAMED_GROUP_CHAT_ID: string =
|
|
148
|
+
"19:2b3c4d5e6f708192a3b4c5d6e7f80912@thread.v2";
|
|
149
|
+
// Also named; Graph keeps answering with the name already stored.
|
|
150
|
+
const QUIET_GROUP_CHAT_ID: string =
|
|
151
|
+
"19:4d5e6f708192a3b4c5d6e7f809123a4b@thread.v2";
|
|
152
|
+
// A group chat in which Microsoft refuses to share the name.
|
|
153
|
+
const DENIED_GROUP_CHAT_ID: string =
|
|
154
|
+
"19:0f1e2d3c4b5a49687f6e5d4c3b2a1908@thread.v2";
|
|
155
|
+
const PERSONAL_CHAT_ID: string = "a:1XyZpersonalChatIdOnlyTheBotFrameworkKnows";
|
|
156
|
+
// A chat the bot is added to during a test.
|
|
157
|
+
const NEW_GROUP_CHAT_ID: string =
|
|
158
|
+
"19:5e4d3c2b1a0948f7e6d5c4b3a2918070@thread.v2";
|
|
159
|
+
|
|
160
|
+
// Six group chats, so Refresh Chats (five lookups at a time) runs two batches.
|
|
161
|
+
const SEEDED_GROUP_CHAT_IDS: Array<string> = [
|
|
162
|
+
LEGACY_GROUP_CHAT_ID,
|
|
163
|
+
FIRST_RELEASE_GROUP_CHAT_ID,
|
|
164
|
+
ROSTER_GROUP_CHAT_ID,
|
|
165
|
+
NAMED_GROUP_CHAT_ID,
|
|
166
|
+
QUIET_GROUP_CHAT_ID,
|
|
167
|
+
DENIED_GROUP_CHAT_ID,
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
// A UTF-16 surrogate half without its partner - what Postgres jsonb refuses.
|
|
171
|
+
const LONE_SURROGATE: RegExp =
|
|
172
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
173
|
+
|
|
174
|
+
interface SeededRow {
|
|
175
|
+
id: ObjectID;
|
|
176
|
+
projectId: ObjectID;
|
|
177
|
+
tenantId: string;
|
|
178
|
+
authToken: string;
|
|
179
|
+
miscData: MicrosoftTeamsMiscData;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
type SqlRow = Record<string, unknown>;
|
|
183
|
+
|
|
184
|
+
type GraphAnswer = HTTPResponse<JSONObject> | HTTPErrorResponse;
|
|
185
|
+
|
|
186
|
+
type GraphAnswerFactory = () => Promise<GraphAnswer>;
|
|
187
|
+
|
|
188
|
+
interface Deferred {
|
|
189
|
+
promise: Promise<void>;
|
|
190
|
+
resolve: () => void;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
* Records every statement Postgres rejected. PostgresQueryRunner hands each
|
|
195
|
+
* failure to the logger before it throws, whatever the caller then does with
|
|
196
|
+
* the error.
|
|
197
|
+
*/
|
|
198
|
+
class FailedQueryRecorder implements Logger {
|
|
199
|
+
public failures: Array<string> = [];
|
|
200
|
+
|
|
201
|
+
public logQuery(): void {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public logQueryError(error: string | Error, query: string): void {
|
|
206
|
+
this.failures.push(
|
|
207
|
+
`${error instanceof Error ? error.message : error} in: ${query}`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public logQuerySlow(): void {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public logSchemaBuild(): void {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public logMigration(): void {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public log(): void {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function legacyGroupChat(): MicrosoftTeamsChat {
|
|
229
|
+
return {
|
|
230
|
+
id: LEGACY_GROUP_CHAT_ID,
|
|
231
|
+
name: "Alice Example, Bob Example",
|
|
232
|
+
chatType: "groupChat",
|
|
233
|
+
serviceUrl: SERVICE_URL,
|
|
234
|
+
addedAt: "2026-03-01T09:00:00.000Z",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function firstReleaseGroupChat(): MicrosoftTeamsChat {
|
|
239
|
+
return {
|
|
240
|
+
id: FIRST_RELEASE_GROUP_CHAT_ID,
|
|
241
|
+
name: "Uma Example, Victor Example, Wendy Example + 1 more",
|
|
242
|
+
chatType: "groupChat",
|
|
243
|
+
serviceUrl: SERVICE_URL,
|
|
244
|
+
addedAt: "2026-08-15T09:00:00.000Z",
|
|
245
|
+
memberAadObjectIds: ["aad-uma", "aad-victor", "aad-wendy", "aad-xavier"],
|
|
246
|
+
memberNames: [
|
|
247
|
+
"Uma Example",
|
|
248
|
+
"Victor Example",
|
|
249
|
+
"Wendy Example",
|
|
250
|
+
"Xavier Example",
|
|
251
|
+
],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function rosterGroupChat(
|
|
256
|
+
overrides?: Partial<MicrosoftTeamsChat>,
|
|
257
|
+
): MicrosoftTeamsChat {
|
|
258
|
+
return {
|
|
259
|
+
id: ROSTER_GROUP_CHAT_ID,
|
|
260
|
+
name: "Carol Example, Dan Example, Erin Example + 2 more",
|
|
261
|
+
chatType: "groupChat",
|
|
262
|
+
serviceUrl: SERVICE_URL,
|
|
263
|
+
addedAt: "2026-09-01T09:00:00.000Z",
|
|
264
|
+
memberAadObjectIds: [
|
|
265
|
+
"aad-carol",
|
|
266
|
+
"aad-dan",
|
|
267
|
+
"aad-erin",
|
|
268
|
+
"aad-ferdinand",
|
|
269
|
+
"aad-gina",
|
|
270
|
+
],
|
|
271
|
+
memberNames: ["Carol Example", "Dan Example", "Erin Example"],
|
|
272
|
+
memberCount: 5,
|
|
273
|
+
...(overrides || {}),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function namedGroupChat(): MicrosoftTeamsChat {
|
|
278
|
+
return {
|
|
279
|
+
id: NAMED_GROUP_CHAT_ID,
|
|
280
|
+
name: "Release Train",
|
|
281
|
+
topic: "Release Train",
|
|
282
|
+
chatType: "groupChat",
|
|
283
|
+
serviceUrl: SERVICE_URL,
|
|
284
|
+
addedAt: "2026-09-04T09:00:00.000Z",
|
|
285
|
+
memberAadObjectIds: ["aad-kim", "aad-lee"],
|
|
286
|
+
memberNames: ["Kim Example", "Lee Example"],
|
|
287
|
+
memberCount: 2,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function quietGroupChat(): MicrosoftTeamsChat {
|
|
292
|
+
return {
|
|
293
|
+
id: QUIET_GROUP_CHAT_ID,
|
|
294
|
+
name: "Status Updates",
|
|
295
|
+
topic: "Status Updates",
|
|
296
|
+
chatType: "groupChat",
|
|
297
|
+
serviceUrl: SERVICE_URL,
|
|
298
|
+
addedAt: "2026-09-05T09:00:00.000Z",
|
|
299
|
+
memberAadObjectIds: ["aad-nina", "aad-omar"],
|
|
300
|
+
memberNames: ["Nina Example", "Omar Example"],
|
|
301
|
+
memberCount: 2,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function deniedGroupChat(): MicrosoftTeamsChat {
|
|
306
|
+
return {
|
|
307
|
+
id: DENIED_GROUP_CHAT_ID,
|
|
308
|
+
name: "Frank Example, Grace Example",
|
|
309
|
+
chatType: "groupChat",
|
|
310
|
+
serviceUrl: SERVICE_URL,
|
|
311
|
+
addedAt: "2026-09-02T09:00:00.000Z",
|
|
312
|
+
memberAadObjectIds: ["aad-frank", "aad-grace"],
|
|
313
|
+
memberNames: ["Frank Example", "Grace Example"],
|
|
314
|
+
memberCount: 2,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function personalChat(): MicrosoftTeamsChat {
|
|
319
|
+
return {
|
|
320
|
+
id: PERSONAL_CHAT_ID,
|
|
321
|
+
name: "Erin Example",
|
|
322
|
+
chatType: "personal",
|
|
323
|
+
serviceUrl: SERVICE_URL,
|
|
324
|
+
addedAt: "2026-09-03T09:00:00.000Z",
|
|
325
|
+
memberAadObjectIds: ["aad-erin"],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function seedChats(): Record<string, MicrosoftTeamsChat> {
|
|
330
|
+
return {
|
|
331
|
+
[LEGACY_GROUP_CHAT_ID]: legacyGroupChat(),
|
|
332
|
+
[FIRST_RELEASE_GROUP_CHAT_ID]: firstReleaseGroupChat(),
|
|
333
|
+
[ROSTER_GROUP_CHAT_ID]: rosterGroupChat(),
|
|
334
|
+
[NAMED_GROUP_CHAT_ID]: namedGroupChat(),
|
|
335
|
+
[QUIET_GROUP_CHAT_ID]: quietGroupChat(),
|
|
336
|
+
[DENIED_GROUP_CHAT_ID]: deniedGroupChat(),
|
|
337
|
+
[PERSONAL_CHAT_ID]: personalChat(),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Everything in miscData besides the chats, which no chat write may touch.
|
|
342
|
+
function seedMiscData(data: {
|
|
343
|
+
tenantId: string;
|
|
344
|
+
teamName: string;
|
|
345
|
+
availableChats: Record<string, MicrosoftTeamsChat>;
|
|
346
|
+
}): MicrosoftTeamsMiscData {
|
|
347
|
+
return {
|
|
348
|
+
tenantId: data.tenantId,
|
|
349
|
+
teamId: "team-1",
|
|
350
|
+
teamName: data.teamName,
|
|
351
|
+
botId: "bot-1",
|
|
352
|
+
adminConsentGranted: true,
|
|
353
|
+
adminConsentGrantedAt: "2026-02-01T00:00:00.000Z",
|
|
354
|
+
availableTeams: {
|
|
355
|
+
"a1b2c3d4-0000-4000-8000-000000000001": {
|
|
356
|
+
id: "a1b2c3d4-0000-4000-8000-000000000001",
|
|
357
|
+
name: "Engineering",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
installedTeams: {
|
|
361
|
+
"a1b2c3d4-0000-4000-8000-000000000001": {
|
|
362
|
+
id: "a1b2c3d4-0000-4000-8000-000000000001",
|
|
363
|
+
graphTeamId: "a1b2c3d4-0000-4000-8000-000000000001",
|
|
364
|
+
teamsThreadId: "19:engineering@thread.tacv2",
|
|
365
|
+
name: "Engineering",
|
|
366
|
+
serviceUrl: SERVICE_URL,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
availableChats: data.availableChats,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function withoutChats(miscData: MicrosoftTeamsMiscData): JSONObject {
|
|
374
|
+
const rest: JSONObject = { ...(miscData as unknown as JSONObject) };
|
|
375
|
+
delete rest["availableChats"];
|
|
376
|
+
return rest;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function graphOk(data: JSONObject): HTTPResponse<JSONObject> {
|
|
380
|
+
return new HTTPResponse<JSONObject>(200, data, {});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function graphError(statusCode: number, body: JSONObject): HTTPErrorResponse {
|
|
384
|
+
return new HTTPErrorResponse(statusCode, body, {});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Graph answers with the chat's name in Teams (null: the chat has none).
|
|
388
|
+
function namedInTeams(topic: string | null): GraphAnswerFactory {
|
|
389
|
+
return async (): Promise<GraphAnswer> => {
|
|
390
|
+
return graphOk({ topic: topic, chatType: "group" });
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// The chat has not granted OneUptime ChatSettings.Read.Chat.
|
|
395
|
+
function refused(): GraphAnswerFactory {
|
|
396
|
+
return async (): Promise<GraphAnswer> => {
|
|
397
|
+
return graphError(403, {
|
|
398
|
+
error: {
|
|
399
|
+
code: "Forbidden",
|
|
400
|
+
message: "Missing role permissions on the request.",
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Graph is having a bad moment.
|
|
407
|
+
function failing(): GraphAnswerFactory {
|
|
408
|
+
return async (): Promise<GraphAnswer> => {
|
|
409
|
+
return graphError(500, {
|
|
410
|
+
error: { code: "InternalServerError", message: "Service down" },
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function everySeededGroupChat(
|
|
416
|
+
answer: GraphAnswerFactory,
|
|
417
|
+
): Record<string, GraphAnswerFactory> {
|
|
418
|
+
const answers: Record<string, GraphAnswerFactory> = {};
|
|
419
|
+
for (const chatId of SEEDED_GROUP_CHAT_IDS) {
|
|
420
|
+
answers[chatId] = answer;
|
|
421
|
+
}
|
|
422
|
+
return answers;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/*
|
|
426
|
+
* What Teams says about each seeded group chat in the refresh tests: two
|
|
427
|
+
* chats have a name OneUptime does not show yet, one has no name (and is
|
|
428
|
+
* already listed by its members), one's name cannot be read right now, one
|
|
429
|
+
* is already up to date, and one refuses.
|
|
430
|
+
*/
|
|
431
|
+
function refreshAnswers(): Record<string, GraphAnswerFactory> {
|
|
432
|
+
return {
|
|
433
|
+
[LEGACY_GROUP_CHAT_ID]: namedInTeams("Payments War Room"),
|
|
434
|
+
[FIRST_RELEASE_GROUP_CHAT_ID]: namedInTeams(null),
|
|
435
|
+
[ROSTER_GROUP_CHAT_ID]: namedInTeams(" Platform On-Call "),
|
|
436
|
+
[NAMED_GROUP_CHAT_ID]: failing(),
|
|
437
|
+
[QUIET_GROUP_CHAT_ID]: namedInTeams("Status Updates"),
|
|
438
|
+
[DENIED_GROUP_CHAT_ID]: refused(),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// The Graph request OneUptime makes for a chat: no $select, which would 400.
|
|
443
|
+
function graphChatUrl(chatId: string): string {
|
|
444
|
+
return `https://graph.microsoft.com/v1.0/chats/${encodeURIComponent(chatId)}`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function deferred(): Deferred {
|
|
448
|
+
let resolve: () => void = () => {
|
|
449
|
+
return;
|
|
450
|
+
};
|
|
451
|
+
const promise: Promise<void> = new Promise<void>((done: () => void) => {
|
|
452
|
+
resolve = done;
|
|
453
|
+
});
|
|
454
|
+
return { promise: promise, resolve: resolve };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
interface FakeTurnContext {
|
|
458
|
+
turnContext: TurnContext;
|
|
459
|
+
sendActivity: jest.Mock;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function buildTurnContext(): FakeTurnContext {
|
|
463
|
+
const sendActivity: jest.Mock = jest.fn(async () => {
|
|
464
|
+
return { id: "welcome-card" };
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
turnContext: {
|
|
469
|
+
activity: {
|
|
470
|
+
recipient: { id: BOT_RECIPIENT_ID },
|
|
471
|
+
serviceUrl: SERVICE_URL,
|
|
472
|
+
conversation: { id: "ctx-conversation-id" },
|
|
473
|
+
},
|
|
474
|
+
turnState: new Map<string, unknown>(),
|
|
475
|
+
sendActivity: sendActivity,
|
|
476
|
+
} as unknown as TurnContext,
|
|
477
|
+
sendActivity: sendActivity,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function member(
|
|
482
|
+
id: string,
|
|
483
|
+
name: string,
|
|
484
|
+
aadObjectId?: string,
|
|
485
|
+
): Partial<TeamsChannelAccount> {
|
|
486
|
+
return aadObjectId
|
|
487
|
+
? { id: id, name: name, aadObjectId: aadObjectId }
|
|
488
|
+
: { id: id, name: name };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function mockRoster(members: Array<Partial<TeamsChannelAccount>>): void {
|
|
492
|
+
(TeamsInfo.getPagedMembers as jest.Mock).mockResolvedValue({
|
|
493
|
+
members: [
|
|
494
|
+
member(BOT_RECIPIENT_ID, "OneUptime"),
|
|
495
|
+
...members,
|
|
496
|
+
] as Array<TeamsChannelAccount>,
|
|
497
|
+
continuationToken: undefined,
|
|
498
|
+
} as unknown as TeamsPagedMembersResult);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function mockRosterUnavailable(): void {
|
|
502
|
+
(TeamsInfo.getPagedMembers as jest.Mock).mockRejectedValue(
|
|
503
|
+
new Error("The bot is not part of the conversation roster."),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function groupChatActivity(data: {
|
|
508
|
+
chatId: string;
|
|
509
|
+
tenantId: string;
|
|
510
|
+
name?: string | undefined;
|
|
511
|
+
botAdded?: boolean | undefined;
|
|
512
|
+
action?: string | undefined;
|
|
513
|
+
serviceUrl?: string | undefined;
|
|
514
|
+
}): JSONObject {
|
|
515
|
+
const conversation: JSONObject = {
|
|
516
|
+
conversationType: "groupChat",
|
|
517
|
+
id: data.chatId,
|
|
518
|
+
tenantId: data.tenantId,
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
if (data.name !== undefined) {
|
|
522
|
+
conversation["name"] = data.name;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
...(data.botAdded ? { membersAdded: [{ id: BOT_RECIPIENT_ID }] } : {}),
|
|
527
|
+
...(data.action ? { action: data.action } : {}),
|
|
528
|
+
conversation: conversation,
|
|
529
|
+
channelData: { tenant: { id: data.tenantId } },
|
|
530
|
+
serviceUrl: data.serviceUrl || SERVICE_URL,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
describePostgres(
|
|
535
|
+
"Microsoft Teams group chat names (#4106) against a migrated Postgres",
|
|
536
|
+
() => {
|
|
537
|
+
const schema: string = `teams_chat_names_${ObjectID.generate()
|
|
538
|
+
.toString()
|
|
539
|
+
.replace(/-/g, "")}`;
|
|
540
|
+
|
|
541
|
+
const failedQueries: FailedQueryRecorder = new FailedQueryRecorder();
|
|
542
|
+
|
|
543
|
+
let database: DataSource;
|
|
544
|
+
|
|
545
|
+
// Two projects connected to the same tenant, and one to another tenant.
|
|
546
|
+
let projectA: SeededRow;
|
|
547
|
+
let projectB: SeededRow;
|
|
548
|
+
let otherTenantProject: SeededRow;
|
|
549
|
+
|
|
550
|
+
// Graph's answer per chat id; a chat left out is an unexpected call.
|
|
551
|
+
let graphAnswers: Record<string, GraphAnswerFactory> = {};
|
|
552
|
+
let graphRequests: Array<APIRequestOptions> = [];
|
|
553
|
+
let tokenSpy: jest.SpyInstance;
|
|
554
|
+
|
|
555
|
+
beforeAll(async () => {
|
|
556
|
+
database = new DataSource({
|
|
557
|
+
type: "postgres",
|
|
558
|
+
host:
|
|
559
|
+
process.env["MICROSOFT_TEAMS_CHAT_TEST_DATABASE_HOST"] || "localhost",
|
|
560
|
+
port: Number(
|
|
561
|
+
process.env["MICROSOFT_TEAMS_CHAT_TEST_DATABASE_PORT"] || "5400",
|
|
562
|
+
),
|
|
563
|
+
username: process.env["DATABASE_USERNAME"] || "postgres",
|
|
564
|
+
password: process.env["DATABASE_PASSWORD"] || "password",
|
|
565
|
+
database:
|
|
566
|
+
process.env["MICROSOFT_TEAMS_CHAT_TEST_DATABASE_NAME"] ||
|
|
567
|
+
process.env["DATABASE_NAME"] ||
|
|
568
|
+
"oneuptimedb",
|
|
569
|
+
entities: Entities,
|
|
570
|
+
schema,
|
|
571
|
+
synchronize: false,
|
|
572
|
+
logging: ["error"],
|
|
573
|
+
logger: failedQueries,
|
|
574
|
+
extra: { options: `-c search_path=${schema},public` },
|
|
575
|
+
});
|
|
576
|
+
await database.initialize();
|
|
577
|
+
|
|
578
|
+
await database.query(`CREATE SCHEMA "${schema}"`);
|
|
579
|
+
await database.query(
|
|
580
|
+
`CREATE TABLE "${schema}"."${TABLE}" (LIKE public."${TABLE}" INCLUDING ALL)`,
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
const currentSchema: Array<{ current_schema: string }> =
|
|
584
|
+
await database.query("SELECT current_schema()");
|
|
585
|
+
expect(currentSchema[0]?.current_schema).toBe(schema);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
beforeEach(async () => {
|
|
589
|
+
failedQueries.failures = [];
|
|
590
|
+
graphRequests = [];
|
|
591
|
+
graphAnswers = {};
|
|
592
|
+
|
|
593
|
+
jest.spyOn(PostgresAppInstance, "isConnected").mockReturnValue(true);
|
|
594
|
+
jest
|
|
595
|
+
.spyOn(PostgresAppInstance, "getDataSource")
|
|
596
|
+
.mockReturnValue(database);
|
|
597
|
+
|
|
598
|
+
(TeamsInfo.getPagedMembers as jest.Mock).mockReset();
|
|
599
|
+
(TeamsInfo.getMembers as jest.Mock).mockReset();
|
|
600
|
+
|
|
601
|
+
jest
|
|
602
|
+
.spyOn(API, "get")
|
|
603
|
+
.mockImplementation(
|
|
604
|
+
async (options: APIRequestOptions): Promise<GraphAnswer> => {
|
|
605
|
+
graphRequests.push(options);
|
|
606
|
+
const url: string = options.url.toString();
|
|
607
|
+
for (const [chatId, answer] of Object.entries(graphAnswers)) {
|
|
608
|
+
if (url === graphChatUrl(chatId)) {
|
|
609
|
+
return answer();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
throw new Error(`Unexpected Graph call in test: ${url}`);
|
|
613
|
+
},
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
// The stored app tokens are fresh; minting a new one would be a bug here.
|
|
617
|
+
jest.spyOn(API, "post").mockImplementation(async () => {
|
|
618
|
+
throw new Error("Unexpected token request in test");
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// Counted, not faked: the real one reads the token off the row.
|
|
622
|
+
tokenSpy = jest.spyOn(MicrosoftTeamsUtil, "getValidAccessToken");
|
|
623
|
+
|
|
624
|
+
await database.query(`DELETE FROM "${schema}"."${TABLE}"`);
|
|
625
|
+
|
|
626
|
+
projectA = await seedRow({
|
|
627
|
+
tenantId: TENANT_ID,
|
|
628
|
+
teamName: "Engineering (project A)",
|
|
629
|
+
availableChats: seedChats(),
|
|
630
|
+
});
|
|
631
|
+
projectB = await seedRow({
|
|
632
|
+
tenantId: TENANT_ID,
|
|
633
|
+
teamName: "Engineering (project B)",
|
|
634
|
+
availableChats: seedChats(),
|
|
635
|
+
});
|
|
636
|
+
// Holds chats under the SAME ids, so a tenant filter left off shows.
|
|
637
|
+
otherTenantProject = await seedRow({
|
|
638
|
+
tenantId: OTHER_TENANT_ID,
|
|
639
|
+
teamName: "Someone else's tenant",
|
|
640
|
+
availableChats: {
|
|
641
|
+
[ROSTER_GROUP_CHAT_ID]: rosterGroupChat({
|
|
642
|
+
name: "Another tenant's chat",
|
|
643
|
+
}),
|
|
644
|
+
[LEGACY_GROUP_CHAT_ID]: legacyGroupChat(),
|
|
645
|
+
[NAMED_GROUP_CHAT_ID]: namedGroupChat(),
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
afterEach(() => {
|
|
651
|
+
jest.restoreAllMocks();
|
|
652
|
+
|
|
653
|
+
// A failed statement fails the test, even if a caller swallowed it.
|
|
654
|
+
expect(failedQueries.failures).toEqual([]);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
afterAll(async () => {
|
|
658
|
+
jest.restoreAllMocks();
|
|
659
|
+
if (database?.isInitialized) {
|
|
660
|
+
await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
661
|
+
await database.destroy();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
async function seedRow(data: {
|
|
666
|
+
tenantId: string;
|
|
667
|
+
teamName: string;
|
|
668
|
+
availableChats: Record<string, MicrosoftTeamsChat>;
|
|
669
|
+
}): Promise<SeededRow> {
|
|
670
|
+
const row: SeededRow = {
|
|
671
|
+
id: ObjectID.generate(),
|
|
672
|
+
projectId: ObjectID.generate(),
|
|
673
|
+
tenantId: data.tenantId,
|
|
674
|
+
authToken: `graph.${ObjectID.generate().toString()}.token`,
|
|
675
|
+
miscData: seedMiscData(data),
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
await database.query(
|
|
679
|
+
`INSERT INTO "${schema}"."${TABLE}"
|
|
680
|
+
("_id", "projectId", "authToken", "authTokenExpiresAt", "workspaceType",
|
|
681
|
+
"workspaceProjectId", "miscData", "version")
|
|
682
|
+
VALUES ($1, $2, $3, now() + interval '1 day', $4, $5, $6, 1)`,
|
|
683
|
+
[
|
|
684
|
+
row.id.toString(),
|
|
685
|
+
row.projectId.toString(),
|
|
686
|
+
row.authToken,
|
|
687
|
+
WorkspaceType.MicrosoftTeams,
|
|
688
|
+
row.tenantId,
|
|
689
|
+
JSON.stringify(row.miscData),
|
|
690
|
+
],
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
return row;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// The row as Postgres holds it, not as any service returns it.
|
|
697
|
+
async function storedRow(row: SeededRow): Promise<SqlRow> {
|
|
698
|
+
const rows: Array<SqlRow> = await database.query(
|
|
699
|
+
`SELECT "miscData", "version", "updatedAt" FROM "${schema}"."${TABLE}" WHERE "_id" = $1`,
|
|
700
|
+
[row.id.toString()],
|
|
701
|
+
);
|
|
702
|
+
expect(rows).toHaveLength(1);
|
|
703
|
+
return rows[0]!;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function storedRows(): Promise<Array<SqlRow>> {
|
|
707
|
+
return [await storedRow(projectA), await storedRow(projectB)];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function storedMiscData(
|
|
711
|
+
row: SeededRow,
|
|
712
|
+
): Promise<MicrosoftTeamsMiscData> {
|
|
713
|
+
return (await storedRow(row))["miscData"] as MicrosoftTeamsMiscData;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function storedChats(
|
|
717
|
+
row: SeededRow,
|
|
718
|
+
): Promise<Record<string, MicrosoftTeamsChat>> {
|
|
719
|
+
return (await storedMiscData(row)).availableChats || {};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async function storedChat(
|
|
723
|
+
row: SeededRow,
|
|
724
|
+
chatId: string,
|
|
725
|
+
): Promise<MicrosoftTeamsChat | undefined> {
|
|
726
|
+
return (await storedChats(row))[chatId];
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Each tenant row's optimistic-lock version: one write bumps it by one.
|
|
730
|
+
async function tenantRowVersions(): Promise<Array<number>> {
|
|
731
|
+
return (await storedRows()).map((row: SqlRow) => {
|
|
732
|
+
return Number(row["version"]);
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Whether the stored chat record has the key at all, as jsonb sees it.
|
|
737
|
+
async function storedChatHasKey(
|
|
738
|
+
row: SeededRow,
|
|
739
|
+
chatId: string,
|
|
740
|
+
key: string,
|
|
741
|
+
): Promise<boolean> {
|
|
742
|
+
const rows: Array<{ present: boolean }> = await database.query(
|
|
743
|
+
`SELECT jsonb_exists("miscData"->'availableChats'->($1::text), $2::text) AS "present"
|
|
744
|
+
FROM "${schema}"."${TABLE}" WHERE "_id" = $3`,
|
|
745
|
+
[chatId, key, row.id.toString()],
|
|
746
|
+
);
|
|
747
|
+
return rows[0]!.present;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function isChatCapturedForTenant(data: {
|
|
751
|
+
tenantId: string;
|
|
752
|
+
chatId: string;
|
|
753
|
+
serviceUrl?: string | undefined;
|
|
754
|
+
}): Promise<boolean> {
|
|
755
|
+
return (MicrosoftTeamsUtil as any).isChatCapturedForTenant(data);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function captureFromMessage(activity: JSONObject): Promise<void> {
|
|
759
|
+
// What handleBotMessageActivity runs for every chat message.
|
|
760
|
+
return (MicrosoftTeamsUtil as any).captureChatFromBotActivity({
|
|
761
|
+
activity: activity,
|
|
762
|
+
turnContext: buildTurnContext().turnContext,
|
|
763
|
+
onlyIfMissingOrStale: true,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function botAddedToChat(data: {
|
|
768
|
+
chatId: string;
|
|
769
|
+
name?: string | undefined;
|
|
770
|
+
}): Promise<void> {
|
|
771
|
+
return MicrosoftTeamsUtil.handleConversationUpdateActivity({
|
|
772
|
+
activity: groupChatActivity({
|
|
773
|
+
chatId: data.chatId,
|
|
774
|
+
tenantId: TENANT_ID,
|
|
775
|
+
name: data.name,
|
|
776
|
+
botAdded: true,
|
|
777
|
+
}),
|
|
778
|
+
turnContext: buildTurnContext().turnContext,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function graphRequestUrls(): Array<string> {
|
|
783
|
+
return graphRequests
|
|
784
|
+
.map((request: APIRequestOptions) => {
|
|
785
|
+
return request.url.toString();
|
|
786
|
+
})
|
|
787
|
+
.sort();
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
async function expectOtherTenantUntouched(): Promise<void> {
|
|
791
|
+
const row: SqlRow = await storedRow(otherTenantProject);
|
|
792
|
+
expect(row["miscData"]).toEqual(otherTenantProject.miscData);
|
|
793
|
+
expect(Number(row["version"])).toBe(1);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async function expectRestOfMiscDataKept(row: SeededRow): Promise<void> {
|
|
797
|
+
expect(withoutChats(await storedMiscData(row))).toEqual(
|
|
798
|
+
withoutChats(row.miscData),
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
describe("capture (bot added / message backfill)", () => {
|
|
803
|
+
test("bot added to a named group chat: every project of the tenant stores the Teams name, the first three member names and the member count", async () => {
|
|
804
|
+
mockRoster([
|
|
805
|
+
member("29:heidi", "Heidi Example", "aad-heidi"),
|
|
806
|
+
member("29:ivan", "Ivan Example", "aad-ivan"),
|
|
807
|
+
member("29:judy", "Judy Example", "aad-judy"),
|
|
808
|
+
// A member Teams sent without a display name is not counted.
|
|
809
|
+
member("29:blank", " ", "aad-blank"),
|
|
810
|
+
member("29:karl", "Karl Example", "aad-karl"),
|
|
811
|
+
]);
|
|
812
|
+
graphAnswers = {
|
|
813
|
+
[NEW_GROUP_CHAT_ID]: namedInTeams(" Incident Bridge "),
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const { turnContext, sendActivity }: FakeTurnContext =
|
|
817
|
+
buildTurnContext();
|
|
818
|
+
|
|
819
|
+
await MicrosoftTeamsUtil.handleConversationUpdateActivity({
|
|
820
|
+
activity: groupChatActivity({
|
|
821
|
+
chatId: NEW_GROUP_CHAT_ID,
|
|
822
|
+
tenantId: TENANT_ID,
|
|
823
|
+
botAdded: true,
|
|
824
|
+
}),
|
|
825
|
+
turnContext: turnContext,
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// One Graph read, with the tenant's app token from one of its rows.
|
|
829
|
+
expect(graphRequestUrls()).toEqual([graphChatUrl(NEW_GROUP_CHAT_ID)]);
|
|
830
|
+
expect([
|
|
831
|
+
`Bearer ${projectA.authToken}`,
|
|
832
|
+
`Bearer ${projectB.authToken}`,
|
|
833
|
+
]).toContain(graphRequests[0]?.headers?.["Authorization"]);
|
|
834
|
+
expect(tokenSpy).toHaveBeenCalledTimes(1);
|
|
835
|
+
|
|
836
|
+
const storedA: MicrosoftTeamsChat | undefined = await storedChat(
|
|
837
|
+
projectA,
|
|
838
|
+
NEW_GROUP_CHAT_ID,
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
expect(storedA).toEqual({
|
|
842
|
+
id: NEW_GROUP_CHAT_ID,
|
|
843
|
+
name: "Incident Bridge",
|
|
844
|
+
topic: "Incident Bridge",
|
|
845
|
+
chatType: "groupChat",
|
|
846
|
+
serviceUrl: SERVICE_URL,
|
|
847
|
+
addedAt: expect.any(String),
|
|
848
|
+
memberAadObjectIds: [
|
|
849
|
+
"aad-heidi",
|
|
850
|
+
"aad-ivan",
|
|
851
|
+
"aad-judy",
|
|
852
|
+
"aad-blank",
|
|
853
|
+
"aad-karl",
|
|
854
|
+
],
|
|
855
|
+
// Only what a chat named after its members shows; not the roster.
|
|
856
|
+
memberNames: ["Heidi Example", "Ivan Example", "Judy Example"],
|
|
857
|
+
memberCount: 4,
|
|
858
|
+
});
|
|
859
|
+
expect(Number.isNaN(Date.parse(storedA!.addedAt!))).toBe(false);
|
|
860
|
+
|
|
861
|
+
for (const row of [projectA, projectB]) {
|
|
862
|
+
expect(await storedChats(row)).toEqual({
|
|
863
|
+
...seedChats(),
|
|
864
|
+
[NEW_GROUP_CHAT_ID]: storedA,
|
|
865
|
+
});
|
|
866
|
+
await expectRestOfMiscDataKept(row);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// As Postgres sees them: a three-name array and a numeric count.
|
|
870
|
+
const shapes: Array<SqlRow> = await database.query(
|
|
871
|
+
`SELECT jsonb_array_length("miscData"->'availableChats'->($1::text)->'memberNames') AS "names",
|
|
872
|
+
jsonb_typeof("miscData"->'availableChats'->($1::text)->'memberCount') AS "countType",
|
|
873
|
+
("miscData"->'availableChats'->($1::text)->>'memberCount')::int AS "count"
|
|
874
|
+
FROM "${schema}"."${TABLE}" WHERE "workspaceProjectId" = $2`,
|
|
875
|
+
[NEW_GROUP_CHAT_ID, TENANT_ID],
|
|
876
|
+
);
|
|
877
|
+
expect(shapes).toEqual([
|
|
878
|
+
{ names: 3, countType: "number", count: 4 },
|
|
879
|
+
{ names: 3, countType: "number", count: 4 },
|
|
880
|
+
]);
|
|
881
|
+
|
|
882
|
+
await expectOtherTenantUntouched();
|
|
883
|
+
|
|
884
|
+
// The welcome card still goes out.
|
|
885
|
+
expect(sendActivity).toHaveBeenCalledTimes(1);
|
|
886
|
+
|
|
887
|
+
// Captured with its member count, so the backfill leaves it alone now.
|
|
888
|
+
await expect(
|
|
889
|
+
isChatCapturedForTenant({
|
|
890
|
+
tenantId: TENANT_ID,
|
|
891
|
+
chatId: NEW_GROUP_CHAT_ID,
|
|
892
|
+
serviceUrl: SERVICE_URL,
|
|
893
|
+
}),
|
|
894
|
+
).resolves.toBe(true);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test("a name on the activity is used as is, without asking Graph", async () => {
|
|
898
|
+
mockRoster([member("29:heidi", "Heidi Example", "aad-heidi")]);
|
|
899
|
+
|
|
900
|
+
await botAddedToChat({
|
|
901
|
+
chatId: NEW_GROUP_CHAT_ID,
|
|
902
|
+
name: " Release Train ",
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
expect(graphRequests).toEqual([]);
|
|
906
|
+
expect(tokenSpy).not.toHaveBeenCalled();
|
|
907
|
+
for (const row of [projectA, projectB]) {
|
|
908
|
+
expect(await storedChat(row, NEW_GROUP_CHAT_ID)).toMatchObject({
|
|
909
|
+
name: "Release Train",
|
|
910
|
+
topic: "Release Train",
|
|
911
|
+
memberNames: ["Heidi Example"],
|
|
912
|
+
memberCount: 1,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
test("bot added where Graph refuses the name: the chat is stored under its members, with no Teams name", async () => {
|
|
918
|
+
mockRoster([
|
|
919
|
+
member("29:heidi", "Heidi Example", "aad-heidi"),
|
|
920
|
+
member("29:ivan", "Ivan Example", "aad-ivan"),
|
|
921
|
+
member("29:judy", "Judy Example", "aad-judy"),
|
|
922
|
+
member("29:karl", "Karl Example", "aad-karl"),
|
|
923
|
+
member("29:lena", "Lena Example", "aad-lena"),
|
|
924
|
+
]);
|
|
925
|
+
graphAnswers = { [NEW_GROUP_CHAT_ID]: refused() };
|
|
926
|
+
|
|
927
|
+
await botAddedToChat({ chatId: NEW_GROUP_CHAT_ID });
|
|
928
|
+
|
|
929
|
+
for (const row of [projectA, projectB]) {
|
|
930
|
+
const chat: MicrosoftTeamsChat | undefined = await storedChat(
|
|
931
|
+
row,
|
|
932
|
+
NEW_GROUP_CHAT_ID,
|
|
933
|
+
);
|
|
934
|
+
expect(chat?.name).toBe(
|
|
935
|
+
"Heidi Example, Ivan Example, Judy Example + 2 more",
|
|
936
|
+
);
|
|
937
|
+
expect(chat?.memberNames).toEqual([
|
|
938
|
+
"Heidi Example",
|
|
939
|
+
"Ivan Example",
|
|
940
|
+
"Judy Example",
|
|
941
|
+
]);
|
|
942
|
+
expect(chat?.memberCount).toBe(5);
|
|
943
|
+
expect(await storedChatHasKey(row, NEW_GROUP_CHAT_ID, "topic")).toBe(
|
|
944
|
+
false,
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
await expectOtherTenantUntouched();
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test("a re-capture keeps the connection date and the stored Teams name when Graph refuses (403) or fails (500)", async () => {
|
|
951
|
+
// The bot is removed and added back; Graph now refuses the name.
|
|
952
|
+
mockRoster([
|
|
953
|
+
member("29:kim", "Kim Example", "aad-kim"),
|
|
954
|
+
member("29:lee", "Lee Example", "aad-lee"),
|
|
955
|
+
member("29:mia", "Mia Example", "aad-mia"),
|
|
956
|
+
]);
|
|
957
|
+
graphAnswers = { [NAMED_GROUP_CHAT_ID]: refused() };
|
|
958
|
+
|
|
959
|
+
await botAddedToChat({ chatId: NAMED_GROUP_CHAT_ID });
|
|
960
|
+
|
|
961
|
+
const afterRefusal: MicrosoftTeamsChat = {
|
|
962
|
+
...namedGroupChat(),
|
|
963
|
+
// The roster is new...
|
|
964
|
+
memberAadObjectIds: ["aad-kim", "aad-lee", "aad-mia"],
|
|
965
|
+
memberNames: ["Kim Example", "Lee Example", "Mia Example"],
|
|
966
|
+
memberCount: 3,
|
|
967
|
+
// ...the Teams name and the date it was first connected are not.
|
|
968
|
+
name: "Release Train",
|
|
969
|
+
topic: "Release Train",
|
|
970
|
+
addedAt: "2026-09-04T09:00:00.000Z",
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
for (const row of [projectA, projectB]) {
|
|
974
|
+
expect(await storedChat(row, NAMED_GROUP_CHAT_ID)).toEqual(
|
|
975
|
+
afterRefusal,
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/*
|
|
980
|
+
* An app upgrade in the chat from another region, while neither the
|
|
981
|
+
* roster nor Graph can be read: only the service URL is news.
|
|
982
|
+
*/
|
|
983
|
+
mockRosterUnavailable();
|
|
984
|
+
graphAnswers = { [NAMED_GROUP_CHAT_ID]: failing() };
|
|
985
|
+
|
|
986
|
+
await MicrosoftTeamsUtil.handleInstallationUpdateActivity({
|
|
987
|
+
activity: groupChatActivity({
|
|
988
|
+
chatId: NAMED_GROUP_CHAT_ID,
|
|
989
|
+
tenantId: TENANT_ID,
|
|
990
|
+
action: "add-upgrade",
|
|
991
|
+
serviceUrl: MOVED_SERVICE_URL,
|
|
992
|
+
}),
|
|
993
|
+
turnContext: buildTurnContext().turnContext,
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
expect(graphRequests).toHaveLength(2);
|
|
997
|
+
for (const row of [projectA, projectB]) {
|
|
998
|
+
expect(await storedChat(row, NAMED_GROUP_CHAT_ID)).toEqual({
|
|
999
|
+
...afterRefusal,
|
|
1000
|
+
serviceUrl: MOVED_SERVICE_URL,
|
|
1001
|
+
});
|
|
1002
|
+
await expectRestOfMiscDataKept(row);
|
|
1003
|
+
}
|
|
1004
|
+
await expectOtherTenantUntouched();
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
test("a legacy group chat whose roster cannot be read and whose name Graph refuses keeps its name, and stays due for re-capture", async () => {
|
|
1008
|
+
mockRosterUnavailable();
|
|
1009
|
+
graphAnswers = { [LEGACY_GROUP_CHAT_ID]: refused() };
|
|
1010
|
+
|
|
1011
|
+
const activity: JSONObject = groupChatActivity({
|
|
1012
|
+
chatId: LEGACY_GROUP_CHAT_ID,
|
|
1013
|
+
tenantId: TENANT_ID,
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
await captureFromMessage(activity);
|
|
1017
|
+
|
|
1018
|
+
expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(1);
|
|
1019
|
+
expect(graphRequests).toHaveLength(1);
|
|
1020
|
+
|
|
1021
|
+
for (const row of [projectA, projectB]) {
|
|
1022
|
+
// Not "Group chat": nothing better was learned, so nothing is lost.
|
|
1023
|
+
expect(await storedChat(row, LEGACY_GROUP_CHAT_ID)).toEqual({
|
|
1024
|
+
...legacyGroupChat(),
|
|
1025
|
+
memberAadObjectIds: [],
|
|
1026
|
+
});
|
|
1027
|
+
for (const key of ["memberCount", "memberNames", "topic"]) {
|
|
1028
|
+
expect(await storedChatHasKey(row, LEGACY_GROUP_CHAT_ID, key)).toBe(
|
|
1029
|
+
false,
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
await expectOtherTenantUntouched();
|
|
1034
|
+
|
|
1035
|
+
// Still no member count, so the next message tries the roster again.
|
|
1036
|
+
await expect(
|
|
1037
|
+
isChatCapturedForTenant({
|
|
1038
|
+
tenantId: TENANT_ID,
|
|
1039
|
+
chatId: LEGACY_GROUP_CHAT_ID,
|
|
1040
|
+
serviceUrl: SERVICE_URL,
|
|
1041
|
+
}),
|
|
1042
|
+
).resolves.toBe(false);
|
|
1043
|
+
|
|
1044
|
+
await captureFromMessage(activity);
|
|
1045
|
+
|
|
1046
|
+
expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(2);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
test("the message backfill re-captures a legacy group chat once, under its Teams name", async () => {
|
|
1050
|
+
mockRoster([
|
|
1051
|
+
member("29:alice", "Alice Example", "aad-alice"),
|
|
1052
|
+
member("29:bob", "Bob Example", "aad-bob"),
|
|
1053
|
+
]);
|
|
1054
|
+
graphAnswers = { [LEGACY_GROUP_CHAT_ID]: namedInTeams("Payments") };
|
|
1055
|
+
|
|
1056
|
+
const activity: JSONObject = groupChatActivity({
|
|
1057
|
+
chatId: LEGACY_GROUP_CHAT_ID,
|
|
1058
|
+
tenantId: TENANT_ID,
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
await captureFromMessage(activity);
|
|
1062
|
+
|
|
1063
|
+
for (const row of [projectA, projectB]) {
|
|
1064
|
+
expect(await storedChat(row, LEGACY_GROUP_CHAT_ID)).toEqual({
|
|
1065
|
+
id: LEGACY_GROUP_CHAT_ID,
|
|
1066
|
+
name: "Payments",
|
|
1067
|
+
topic: "Payments",
|
|
1068
|
+
chatType: "groupChat",
|
|
1069
|
+
serviceUrl: SERVICE_URL,
|
|
1070
|
+
// Hearing from the chat again is not when it was connected.
|
|
1071
|
+
addedAt: "2026-03-01T09:00:00.000Z",
|
|
1072
|
+
memberAadObjectIds: ["aad-alice", "aad-bob"],
|
|
1073
|
+
memberNames: ["Alice Example", "Bob Example"],
|
|
1074
|
+
memberCount: 2,
|
|
1075
|
+
});
|
|
1076
|
+
await expectRestOfMiscDataKept(row);
|
|
1077
|
+
}
|
|
1078
|
+
await expectOtherTenantUntouched();
|
|
1079
|
+
expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(1);
|
|
1080
|
+
expect(graphRequests).toHaveLength(1);
|
|
1081
|
+
|
|
1082
|
+
// The next message finds it captured: no roster call, no Graph call.
|
|
1083
|
+
const before: Array<SqlRow> = await storedRows();
|
|
1084
|
+
|
|
1085
|
+
await captureFromMessage(activity);
|
|
1086
|
+
|
|
1087
|
+
expect(TeamsInfo.getPagedMembers).toHaveBeenCalledTimes(1);
|
|
1088
|
+
expect(graphRequests).toHaveLength(1);
|
|
1089
|
+
expect(await storedRows()).toEqual(before);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
test("the message backfill re-captures a chat the first release stored, keeping only three member names", async () => {
|
|
1093
|
+
mockRoster([
|
|
1094
|
+
member("29:uma", "Uma Example", "aad-uma"),
|
|
1095
|
+
member("29:victor", "Victor Example", "aad-victor"),
|
|
1096
|
+
member("29:wendy", "Wendy Example", "aad-wendy"),
|
|
1097
|
+
member("29:xavier", "Xavier Example", "aad-xavier"),
|
|
1098
|
+
]);
|
|
1099
|
+
graphAnswers = { [FIRST_RELEASE_GROUP_CHAT_ID]: namedInTeams(null) };
|
|
1100
|
+
|
|
1101
|
+
await captureFromMessage(
|
|
1102
|
+
groupChatActivity({
|
|
1103
|
+
chatId: FIRST_RELEASE_GROUP_CHAT_ID,
|
|
1104
|
+
tenantId: TENANT_ID,
|
|
1105
|
+
}),
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
for (const row of [projectA, projectB]) {
|
|
1109
|
+
expect(await storedChat(row, FIRST_RELEASE_GROUP_CHAT_ID)).toEqual({
|
|
1110
|
+
...firstReleaseGroupChat(),
|
|
1111
|
+
// The full roster is no longer kept where every Viewer can read it.
|
|
1112
|
+
memberNames: ["Uma Example", "Victor Example", "Wendy Example"],
|
|
1113
|
+
memberCount: 4,
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
await expect(
|
|
1118
|
+
isChatCapturedForTenant({
|
|
1119
|
+
tenantId: TENANT_ID,
|
|
1120
|
+
chatId: FIRST_RELEASE_GROUP_CHAT_ID,
|
|
1121
|
+
serviceUrl: SERVICE_URL,
|
|
1122
|
+
}),
|
|
1123
|
+
).resolves.toBe(true);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
test("the message backfill leaves a group chat with a member count alone", async () => {
|
|
1127
|
+
const before: Array<SqlRow> = await storedRows();
|
|
1128
|
+
|
|
1129
|
+
await captureFromMessage(
|
|
1130
|
+
groupChatActivity({
|
|
1131
|
+
chatId: ROSTER_GROUP_CHAT_ID,
|
|
1132
|
+
tenantId: TENANT_ID,
|
|
1133
|
+
}),
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
expect(TeamsInfo.getPagedMembers).not.toHaveBeenCalled();
|
|
1137
|
+
expect(graphRequests).toEqual([]);
|
|
1138
|
+
expect(await storedRows()).toEqual(before);
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
describe("names Postgres must store as they are", () => {
|
|
1143
|
+
test("non-ASCII Teams and member names round-trip through jsonb character for character", async () => {
|
|
1144
|
+
const topic: string = "Équipe d'astreinte · 障害対応 🚨";
|
|
1145
|
+
// 28 UTF-16 units, 27 characters: the emoji is one character of two units.
|
|
1146
|
+
const topicCharacters: number = Array.from(topic).length;
|
|
1147
|
+
expect(topicCharacters).toBe(topic.length - 1);
|
|
1148
|
+
const memberNames: Array<string> = [
|
|
1149
|
+
"Zoë Ünïcode-Łukasz",
|
|
1150
|
+
"李雷",
|
|
1151
|
+
"Ольга Пример",
|
|
1152
|
+
"محمد مثال",
|
|
1153
|
+
];
|
|
1154
|
+
|
|
1155
|
+
mockRoster(
|
|
1156
|
+
memberNames.map((name: string, index: number) => {
|
|
1157
|
+
return member(`29:member-${index}`, name, `aad-member-${index}`);
|
|
1158
|
+
}),
|
|
1159
|
+
);
|
|
1160
|
+
graphAnswers = { [NEW_GROUP_CHAT_ID]: namedInTeams(topic) };
|
|
1161
|
+
|
|
1162
|
+
await botAddedToChat({ chatId: NEW_GROUP_CHAT_ID });
|
|
1163
|
+
|
|
1164
|
+
for (const row of [projectA, projectB]) {
|
|
1165
|
+
expect(await storedChat(row, NEW_GROUP_CHAT_ID)).toMatchObject({
|
|
1166
|
+
name: topic,
|
|
1167
|
+
topic: topic,
|
|
1168
|
+
memberNames: memberNames.slice(0, 3),
|
|
1169
|
+
memberCount: 4,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/*
|
|
1174
|
+
* Compared inside Postgres, and counted in characters: text stored as
|
|
1175
|
+
* escapes or re-encoded bytes would differ in length.
|
|
1176
|
+
*/
|
|
1177
|
+
const stored: Array<SqlRow> = await database.query(
|
|
1178
|
+
`SELECT "miscData"->'availableChats'->($1::text)->>'topic' = $2 AS "sameTopic",
|
|
1179
|
+
char_length("miscData"->'availableChats'->($1::text)->>'topic') AS "topicLength",
|
|
1180
|
+
"miscData"->'availableChats'->($1::text)->'memberNames' = $3::jsonb AS "sameMembers"
|
|
1181
|
+
FROM "${schema}"."${TABLE}" WHERE "workspaceProjectId" = $4`,
|
|
1182
|
+
[
|
|
1183
|
+
NEW_GROUP_CHAT_ID,
|
|
1184
|
+
topic,
|
|
1185
|
+
JSON.stringify(memberNames.slice(0, 3)),
|
|
1186
|
+
TENANT_ID,
|
|
1187
|
+
],
|
|
1188
|
+
);
|
|
1189
|
+
expect(stored).toEqual([
|
|
1190
|
+
{ sameTopic: true, topicLength: topicCharacters, sameMembers: true },
|
|
1191
|
+
{ sameTopic: true, topicLength: topicCharacters, sameMembers: true },
|
|
1192
|
+
]);
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
test("a long Teams name cut where an emoji sits is stored, on capture and on refresh, without half an emoji", async () => {
|
|
1196
|
+
// Characters 78 and 79 (0-based UTF-16 units) are the two halves of 🚨.
|
|
1197
|
+
const longTopic: string =
|
|
1198
|
+
"A".repeat(78) + "🚨" + " Payments incident bridge, EMEA and APAC";
|
|
1199
|
+
// 78 characters and the ellipsis: the emoji is dropped whole.
|
|
1200
|
+
const expectedName: string = "A".repeat(78) + "…";
|
|
1201
|
+
|
|
1202
|
+
/*
|
|
1203
|
+
* The probe that makes this test mean something: a name cut by UTF-16
|
|
1204
|
+
* units alone ends in a lone high surrogate, and Postgres refuses the
|
|
1205
|
+
* write that carries it - the chat is not stored at all.
|
|
1206
|
+
*/
|
|
1207
|
+
const naiveCut: string = `${longTopic.substring(0, 79)}…`;
|
|
1208
|
+
expect(LONE_SURROGATE.test(naiveCut)).toBe(true);
|
|
1209
|
+
|
|
1210
|
+
await expect(
|
|
1211
|
+
MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
|
|
1212
|
+
tenantId: TENANT_ID,
|
|
1213
|
+
chat: {
|
|
1214
|
+
id: NEW_GROUP_CHAT_ID,
|
|
1215
|
+
name: naiveCut,
|
|
1216
|
+
chatType: "groupChat",
|
|
1217
|
+
serviceUrl: SERVICE_URL,
|
|
1218
|
+
addedAt: "2026-09-28T10:00:00.000Z",
|
|
1219
|
+
memberAadObjectIds: [],
|
|
1220
|
+
},
|
|
1221
|
+
}),
|
|
1222
|
+
).rejects.toMatchObject({
|
|
1223
|
+
detail: "Unicode low surrogate must follow a high surrogate.",
|
|
1224
|
+
});
|
|
1225
|
+
expect(failedQueries.failures).toHaveLength(1);
|
|
1226
|
+
expect(failedQueries.failures[0]).toContain(
|
|
1227
|
+
"invalid input syntax for type json",
|
|
1228
|
+
);
|
|
1229
|
+
failedQueries.failures = [];
|
|
1230
|
+
expect(await storedChat(projectA, NEW_GROUP_CHAT_ID)).toBeUndefined();
|
|
1231
|
+
|
|
1232
|
+
// Capture: the bot is added to the chat with the long name.
|
|
1233
|
+
mockRoster([member("29:heidi", "Heidi Example", "aad-heidi")]);
|
|
1234
|
+
graphAnswers = { [NEW_GROUP_CHAT_ID]: namedInTeams(longTopic) };
|
|
1235
|
+
|
|
1236
|
+
await botAddedToChat({ chatId: NEW_GROUP_CHAT_ID });
|
|
1237
|
+
|
|
1238
|
+
for (const row of [projectA, projectB]) {
|
|
1239
|
+
const chat: MicrosoftTeamsChat | undefined = await storedChat(
|
|
1240
|
+
row,
|
|
1241
|
+
NEW_GROUP_CHAT_ID,
|
|
1242
|
+
);
|
|
1243
|
+
expect(chat?.name).toBe(expectedName);
|
|
1244
|
+
// The Teams name itself is kept whole, emoji included.
|
|
1245
|
+
expect(chat?.topic).toBe(longTopic);
|
|
1246
|
+
expect(LONE_SURROGATE.test(chat?.name || "")).toBe(false);
|
|
1247
|
+
expect(LONE_SURROGATE.test(chat?.topic || "")).toBe(false);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Refresh: the chat is renamed in Teams to another long name.
|
|
1251
|
+
const renamedTopic: string = "Ω".repeat(78) + "🚨" + " renamed bridge";
|
|
1252
|
+
graphAnswers = {
|
|
1253
|
+
...everySeededGroupChat(refused()),
|
|
1254
|
+
[NEW_GROUP_CHAT_ID]: namedInTeams(renamedTopic),
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
const result: MicrosoftTeamsChatNameRefreshResult =
|
|
1258
|
+
await MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1259
|
+
projectId: projectA.projectId,
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
expect(result.chats[NEW_GROUP_CHAT_ID]?.name).toBe(
|
|
1263
|
+
"Ω".repeat(78) + "…",
|
|
1264
|
+
);
|
|
1265
|
+
|
|
1266
|
+
const stored: Array<SqlRow> = await database.query(
|
|
1267
|
+
`SELECT "miscData"->'availableChats'->($1::text)->>'name' AS "name",
|
|
1268
|
+
char_length("miscData"->'availableChats'->($1::text)->>'name') AS "nameLength",
|
|
1269
|
+
"miscData"->'availableChats'->($1::text)->>'topic' AS "topic"
|
|
1270
|
+
FROM "${schema}"."${TABLE}" WHERE "workspaceProjectId" = $2`,
|
|
1271
|
+
[NEW_GROUP_CHAT_ID, TENANT_ID],
|
|
1272
|
+
);
|
|
1273
|
+
expect(stored).toEqual([
|
|
1274
|
+
{
|
|
1275
|
+
name: "Ω".repeat(78) + "…",
|
|
1276
|
+
nameLength: 79,
|
|
1277
|
+
topic: renamedTopic,
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
name: "Ω".repeat(78) + "…",
|
|
1281
|
+
nameLength: 79,
|
|
1282
|
+
topic: renamedTopic,
|
|
1283
|
+
},
|
|
1284
|
+
]);
|
|
1285
|
+
});
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
describe("isChatCapturedForTenant (the message backfill's check)", () => {
|
|
1289
|
+
test("reads the stored records: group chats without a member count are stale, the rest are not", async () => {
|
|
1290
|
+
for (const chatId of [
|
|
1291
|
+
LEGACY_GROUP_CHAT_ID,
|
|
1292
|
+
// Stored its names, but no count: re-captured once as well.
|
|
1293
|
+
FIRST_RELEASE_GROUP_CHAT_ID,
|
|
1294
|
+
]) {
|
|
1295
|
+
await expect(
|
|
1296
|
+
isChatCapturedForTenant({
|
|
1297
|
+
tenantId: TENANT_ID,
|
|
1298
|
+
chatId: chatId,
|
|
1299
|
+
serviceUrl: SERVICE_URL,
|
|
1300
|
+
}),
|
|
1301
|
+
).resolves.toBe(false);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
for (const chatId of [
|
|
1305
|
+
ROSTER_GROUP_CHAT_ID,
|
|
1306
|
+
NAMED_GROUP_CHAT_ID,
|
|
1307
|
+
QUIET_GROUP_CHAT_ID,
|
|
1308
|
+
DENIED_GROUP_CHAT_ID,
|
|
1309
|
+
PERSONAL_CHAT_ID,
|
|
1310
|
+
]) {
|
|
1311
|
+
await expect(
|
|
1312
|
+
isChatCapturedForTenant({
|
|
1313
|
+
tenantId: TENANT_ID,
|
|
1314
|
+
chatId: chatId,
|
|
1315
|
+
serviceUrl: SERVICE_URL,
|
|
1316
|
+
}),
|
|
1317
|
+
).resolves.toBe(true);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// A moved serviceUrl is still stale.
|
|
1321
|
+
await expect(
|
|
1322
|
+
isChatCapturedForTenant({
|
|
1323
|
+
tenantId: TENANT_ID,
|
|
1324
|
+
chatId: ROSTER_GROUP_CHAT_ID,
|
|
1325
|
+
serviceUrl: MOVED_SERVICE_URL,
|
|
1326
|
+
}),
|
|
1327
|
+
).resolves.toBe(false);
|
|
1328
|
+
|
|
1329
|
+
// A tenant with no connected project has nothing to capture into.
|
|
1330
|
+
await expect(
|
|
1331
|
+
isChatCapturedForTenant({
|
|
1332
|
+
tenantId: "no-such-tenant",
|
|
1333
|
+
chatId: ROSTER_GROUP_CHAT_ID,
|
|
1334
|
+
serviceUrl: SERVICE_URL,
|
|
1335
|
+
}),
|
|
1336
|
+
).resolves.toBe(true);
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
test("a member count of 0 is a roster that was read, not a missing one", async () => {
|
|
1340
|
+
// A chat whose members Teams sent without display names.
|
|
1341
|
+
await MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
|
|
1342
|
+
tenantId: TENANT_ID,
|
|
1343
|
+
chat: rosterGroupChat({ name: "Group chat", memberNames: [] }),
|
|
1344
|
+
});
|
|
1345
|
+
await database.query(
|
|
1346
|
+
`UPDATE "${schema}"."${TABLE}"
|
|
1347
|
+
SET "miscData" = jsonb_set("miscData", ARRAY['availableChats', $1::text, 'memberCount'], '0'::jsonb)
|
|
1348
|
+
WHERE "workspaceProjectId" = $2`,
|
|
1349
|
+
[ROSTER_GROUP_CHAT_ID, TENANT_ID],
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
for (const row of [projectA, projectB]) {
|
|
1353
|
+
expect(
|
|
1354
|
+
(await storedChat(row, ROSTER_GROUP_CHAT_ID))?.memberCount,
|
|
1355
|
+
).toBe(0);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
await expect(
|
|
1359
|
+
isChatCapturedForTenant({
|
|
1360
|
+
tenantId: TENANT_ID,
|
|
1361
|
+
chatId: ROSTER_GROUP_CHAT_ID,
|
|
1362
|
+
serviceUrl: SERVICE_URL,
|
|
1363
|
+
}),
|
|
1364
|
+
).resolves.toBe(true);
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
test("a chat missing from one project of the tenant is stale", async () => {
|
|
1368
|
+
await database.query(
|
|
1369
|
+
`UPDATE "${schema}"."${TABLE}"
|
|
1370
|
+
SET "miscData" = jsonb_set("miscData", '{availableChats}', ("miscData"->'availableChats') - $1::text)
|
|
1371
|
+
WHERE "_id" = $2`,
|
|
1372
|
+
[ROSTER_GROUP_CHAT_ID, projectB.id.toString()],
|
|
1373
|
+
);
|
|
1374
|
+
|
|
1375
|
+
await expect(
|
|
1376
|
+
isChatCapturedForTenant({
|
|
1377
|
+
tenantId: TENANT_ID,
|
|
1378
|
+
chatId: ROSTER_GROUP_CHAT_ID,
|
|
1379
|
+
serviceUrl: SERVICE_URL,
|
|
1380
|
+
}),
|
|
1381
|
+
).resolves.toBe(false);
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
describe("refreshChatNamesForProject (Refresh Chats)", () => {
|
|
1386
|
+
test("renames on both projects of the tenant with the Teams name, reports refusals and failures by id, and reads the token once", async () => {
|
|
1387
|
+
graphAnswers = refreshAnswers();
|
|
1388
|
+
|
|
1389
|
+
const result: MicrosoftTeamsChatNameRefreshResult =
|
|
1390
|
+
await MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1391
|
+
projectId: projectA.projectId,
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
expect(result.permissionDeniedChatIds).toEqual([DENIED_GROUP_CHAT_ID]);
|
|
1395
|
+
expect(result.failedChatIds).toEqual([NAMED_GROUP_CHAT_ID]);
|
|
1396
|
+
|
|
1397
|
+
/*
|
|
1398
|
+
* One Graph read per group chat, none for the personal chat - two
|
|
1399
|
+
* batches, one token: the one stored on the project's own row.
|
|
1400
|
+
*/
|
|
1401
|
+
expect(graphRequestUrls()).toEqual(
|
|
1402
|
+
SEEDED_GROUP_CHAT_IDS.map(graphChatUrl).sort(),
|
|
1403
|
+
);
|
|
1404
|
+
for (const request of graphRequests) {
|
|
1405
|
+
expect(request.headers?.["Authorization"]).toBe(
|
|
1406
|
+
`Bearer ${projectA.authToken}`,
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
expect(tokenSpy).toHaveBeenCalledTimes(1);
|
|
1410
|
+
|
|
1411
|
+
const expected: Record<string, MicrosoftTeamsChat> = {
|
|
1412
|
+
...seedChats(),
|
|
1413
|
+
[LEGACY_GROUP_CHAT_ID]: {
|
|
1414
|
+
...legacyGroupChat(),
|
|
1415
|
+
name: "Payments War Room",
|
|
1416
|
+
topic: "Payments War Room",
|
|
1417
|
+
},
|
|
1418
|
+
[ROSTER_GROUP_CHAT_ID]: rosterGroupChat({
|
|
1419
|
+
name: "Platform On-Call",
|
|
1420
|
+
topic: "Platform On-Call",
|
|
1421
|
+
}),
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
// What the page is sent is what is stored.
|
|
1425
|
+
expect(result.chats).toEqual(expected);
|
|
1426
|
+
|
|
1427
|
+
for (const row of [projectA, projectB]) {
|
|
1428
|
+
expect(await storedChats(row)).toEqual(expected);
|
|
1429
|
+
// A name-only write: the legacy record gains no roster.
|
|
1430
|
+
for (const key of ["memberCount", "memberNames"]) {
|
|
1431
|
+
expect(await storedChatHasKey(row, LEGACY_GROUP_CHAT_ID, key)).toBe(
|
|
1432
|
+
false,
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
await expectRestOfMiscDataKept(row);
|
|
1436
|
+
|
|
1437
|
+
await expect(
|
|
1438
|
+
MicrosoftTeamsUtil.getChatsForProject({
|
|
1439
|
+
projectId: row.projectId,
|
|
1440
|
+
}),
|
|
1441
|
+
).resolves.toEqual(expected);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Both renames in one write per row.
|
|
1445
|
+
expect(await tenantRowVersions()).toEqual([2, 2]);
|
|
1446
|
+
|
|
1447
|
+
await expectOtherTenantUntouched();
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
test('a name removed in Teams falls back to the stored member names, or to "Group chat" when there are none', async () => {
|
|
1451
|
+
// A named chat whose members all came without display names.
|
|
1452
|
+
const namelessMembers: MicrosoftTeamsChat = {
|
|
1453
|
+
...quietGroupChat(),
|
|
1454
|
+
memberNames: [],
|
|
1455
|
+
memberCount: 0,
|
|
1456
|
+
};
|
|
1457
|
+
await MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
|
|
1458
|
+
tenantId: TENANT_ID,
|
|
1459
|
+
chat: namelessMembers,
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
graphAnswers = {
|
|
1463
|
+
...everySeededGroupChat(namedInTeams(null)),
|
|
1464
|
+
[FIRST_RELEASE_GROUP_CHAT_ID]: refused(),
|
|
1465
|
+
[DENIED_GROUP_CHAT_ID]: refused(),
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
const result: MicrosoftTeamsChatNameRefreshResult =
|
|
1469
|
+
await MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1470
|
+
projectId: projectB.projectId,
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
expect([...result.permissionDeniedChatIds].sort()).toEqual(
|
|
1474
|
+
[FIRST_RELEASE_GROUP_CHAT_ID, DENIED_GROUP_CHAT_ID].sort(),
|
|
1475
|
+
);
|
|
1476
|
+
expect(result.failedChatIds).toEqual([]);
|
|
1477
|
+
|
|
1478
|
+
const namedWithoutTopic: MicrosoftTeamsChat = {
|
|
1479
|
+
...namedGroupChat(),
|
|
1480
|
+
name: "Kim Example, Lee Example",
|
|
1481
|
+
};
|
|
1482
|
+
delete namedWithoutTopic.topic;
|
|
1483
|
+
|
|
1484
|
+
const quietWithoutTopic: MicrosoftTeamsChat = {
|
|
1485
|
+
...namelessMembers,
|
|
1486
|
+
name: "Group chat",
|
|
1487
|
+
};
|
|
1488
|
+
delete quietWithoutTopic.topic;
|
|
1489
|
+
|
|
1490
|
+
for (const row of [projectA, projectB]) {
|
|
1491
|
+
expect(await storedChats(row)).toEqual({
|
|
1492
|
+
...seedChats(),
|
|
1493
|
+
[NAMED_GROUP_CHAT_ID]: namedWithoutTopic,
|
|
1494
|
+
[QUIET_GROUP_CHAT_ID]: quietWithoutTopic,
|
|
1495
|
+
/*
|
|
1496
|
+
* Unchanged, and so not rewritten: the roster chat already shows
|
|
1497
|
+
* its first three members and the count of the rest, and the
|
|
1498
|
+
* legacy chat has no members to fall back to.
|
|
1499
|
+
*/
|
|
1500
|
+
[ROSTER_GROUP_CHAT_ID]: rosterGroupChat(),
|
|
1501
|
+
[LEGACY_GROUP_CHAT_ID]: legacyGroupChat(),
|
|
1502
|
+
});
|
|
1503
|
+
for (const chatId of [NAMED_GROUP_CHAT_ID, QUIET_GROUP_CHAT_ID]) {
|
|
1504
|
+
expect(await storedChatHasKey(row, chatId, "topic")).toBe(false);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
await expectOtherTenantUntouched();
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
test("a chat removed while names were read is neither resurrected nor returned, and one re-captured meanwhile keeps its new record under its Teams name", async () => {
|
|
1512
|
+
const recaptured: MicrosoftTeamsChat = rosterGroupChat({
|
|
1513
|
+
name: "Carol Example, Dan Example, Erin Example + 3 more",
|
|
1514
|
+
serviceUrl: MOVED_SERVICE_URL,
|
|
1515
|
+
memberAadObjectIds: [
|
|
1516
|
+
"aad-carol",
|
|
1517
|
+
"aad-dan",
|
|
1518
|
+
"aad-erin",
|
|
1519
|
+
"aad-ferdinand",
|
|
1520
|
+
"aad-gina",
|
|
1521
|
+
"aad-oscar",
|
|
1522
|
+
],
|
|
1523
|
+
memberCount: 6,
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
/*
|
|
1527
|
+
* Both happen while the lookups are out, one after the other in one
|
|
1528
|
+
* answer: they are read-modify-writes of the same rows, and this test
|
|
1529
|
+
* is about the rename that follows them, not about each other.
|
|
1530
|
+
*/
|
|
1531
|
+
graphAnswers = {
|
|
1532
|
+
...refreshAnswers(),
|
|
1533
|
+
[LEGACY_GROUP_CHAT_ID]: async (): Promise<GraphAnswer> => {
|
|
1534
|
+
// The bot is removed from the chat mid-refresh...
|
|
1535
|
+
await MicrosoftTeamsUtil.removeChatFromProjectAuthTokens({
|
|
1536
|
+
tenantId: TENANT_ID,
|
|
1537
|
+
chatId: LEGACY_GROUP_CHAT_ID,
|
|
1538
|
+
});
|
|
1539
|
+
// ...and someone joins another, which is captured again.
|
|
1540
|
+
await MicrosoftTeamsUtil.saveChatToProjectAuthTokens({
|
|
1541
|
+
tenantId: TENANT_ID,
|
|
1542
|
+
chat: recaptured,
|
|
1543
|
+
});
|
|
1544
|
+
return graphOk({ topic: "Payments War Room" });
|
|
1545
|
+
},
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
const result: MicrosoftTeamsChatNameRefreshResult =
|
|
1549
|
+
await MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1550
|
+
projectId: projectA.projectId,
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
const expected: Record<string, MicrosoftTeamsChat> = seedChats();
|
|
1554
|
+
delete expected[LEGACY_GROUP_CHAT_ID];
|
|
1555
|
+
expected[ROSTER_GROUP_CHAT_ID] = {
|
|
1556
|
+
...recaptured,
|
|
1557
|
+
name: "Platform On-Call",
|
|
1558
|
+
topic: "Platform On-Call",
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
expect(result.chats).toEqual(expected);
|
|
1562
|
+
expect(result.chats[LEGACY_GROUP_CHAT_ID]).toBeUndefined();
|
|
1563
|
+
|
|
1564
|
+
for (const row of [projectA, projectB]) {
|
|
1565
|
+
expect(await storedChats(row)).toEqual(expected);
|
|
1566
|
+
await expectRestOfMiscDataKept(row);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// It holds chats under both ids.
|
|
1570
|
+
await expectOtherTenantUntouched();
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
test("two refreshes of one project at once share one pass over the chats and one write per row", async () => {
|
|
1574
|
+
const lookupStarted: Deferred = deferred();
|
|
1575
|
+
const releaseLookup: Deferred = deferred();
|
|
1576
|
+
|
|
1577
|
+
graphAnswers = {
|
|
1578
|
+
...refreshAnswers(),
|
|
1579
|
+
[LEGACY_GROUP_CHAT_ID]: async (): Promise<GraphAnswer> => {
|
|
1580
|
+
lookupStarted.resolve();
|
|
1581
|
+
await releaseLookup.promise;
|
|
1582
|
+
return graphOk({ topic: "Payments War Room" });
|
|
1583
|
+
},
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
const first: Promise<MicrosoftTeamsChatNameRefreshResult> =
|
|
1587
|
+
MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1588
|
+
projectId: projectA.projectId,
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
// A second click while the first is still reading names.
|
|
1592
|
+
await lookupStarted.promise;
|
|
1593
|
+
const second: Promise<MicrosoftTeamsChatNameRefreshResult> =
|
|
1594
|
+
MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1595
|
+
projectId: projectA.projectId,
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
releaseLookup.resolve();
|
|
1599
|
+
|
|
1600
|
+
const results: Array<MicrosoftTeamsChatNameRefreshResult> =
|
|
1601
|
+
await Promise.all([first, second]);
|
|
1602
|
+
|
|
1603
|
+
expect(results[1]).toEqual(results[0]);
|
|
1604
|
+
expect(results[0]?.chats[LEGACY_GROUP_CHAT_ID]?.name).toBe(
|
|
1605
|
+
"Payments War Room",
|
|
1606
|
+
);
|
|
1607
|
+
expect(graphRequests).toHaveLength(SEEDED_GROUP_CHAT_IDS.length);
|
|
1608
|
+
expect(tokenSpy).toHaveBeenCalledTimes(1);
|
|
1609
|
+
expect(await tenantRowVersions()).toEqual([2, 2]);
|
|
1610
|
+
|
|
1611
|
+
// Once it has settled, the next click runs a refresh of its own.
|
|
1612
|
+
await MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1613
|
+
projectId: projectA.projectId,
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
expect(graphRequests).toHaveLength(2 * SEEDED_GROUP_CHAT_IDS.length);
|
|
1617
|
+
expect(tokenSpy).toHaveBeenCalledTimes(2);
|
|
1618
|
+
// Nothing new to write.
|
|
1619
|
+
expect(await tenantRowVersions()).toEqual([2, 2]);
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
test("a project with no group chats reads no token, makes no Graph call and writes nothing", async () => {
|
|
1623
|
+
await database.query(
|
|
1624
|
+
`UPDATE "${schema}"."${TABLE}"
|
|
1625
|
+
SET "miscData" = jsonb_set("miscData", '{availableChats}', jsonb_build_object($1::text, "miscData"->'availableChats'->($1::text)))
|
|
1626
|
+
WHERE "workspaceProjectId" = $2`,
|
|
1627
|
+
[PERSONAL_CHAT_ID, TENANT_ID],
|
|
1628
|
+
);
|
|
1629
|
+
const before: SqlRow = await storedRow(projectA);
|
|
1630
|
+
|
|
1631
|
+
const result: MicrosoftTeamsChatNameRefreshResult =
|
|
1632
|
+
await MicrosoftTeamsUtil.refreshChatNamesForProject({
|
|
1633
|
+
projectId: projectA.projectId,
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
expect(result).toEqual({
|
|
1637
|
+
chats: { [PERSONAL_CHAT_ID]: personalChat() },
|
|
1638
|
+
permissionDeniedChatIds: [],
|
|
1639
|
+
failedChatIds: [],
|
|
1640
|
+
});
|
|
1641
|
+
expect(graphRequests).toEqual([]);
|
|
1642
|
+
expect(tokenSpy).not.toHaveBeenCalled();
|
|
1643
|
+
expect(await storedRow(projectA)).toEqual(before);
|
|
1644
|
+
});
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
describe("renameChatsInProjectAuthTokens", () => {
|
|
1648
|
+
test("writes the name and Teams name onto chats that exist, drops a removed Teams name, and skips rows it would not change", async () => {
|
|
1649
|
+
const updates: Record<string, MicrosoftTeamsChatNameUpdate> = {
|
|
1650
|
+
[ROSTER_GROUP_CHAT_ID]: {
|
|
1651
|
+
name: "Platform On-Call",
|
|
1652
|
+
topic: "Platform On-Call",
|
|
1653
|
+
},
|
|
1654
|
+
// The chat's name was removed in Teams.
|
|
1655
|
+
[NAMED_GROUP_CHAT_ID]: { name: "Kim Example, Lee Example" },
|
|
1656
|
+
[NEW_GROUP_CHAT_ID]: { name: "Never stored", topic: "Never stored" },
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
await MicrosoftTeamsUtil.renameChatsInProjectAuthTokens({
|
|
1660
|
+
tenantId: TENANT_ID,
|
|
1661
|
+
chatNames: updates,
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
const namedWithoutTopic: MicrosoftTeamsChat = {
|
|
1665
|
+
...namedGroupChat(),
|
|
1666
|
+
name: "Kim Example, Lee Example",
|
|
1667
|
+
};
|
|
1668
|
+
delete namedWithoutTopic.topic;
|
|
1669
|
+
|
|
1670
|
+
for (const row of [projectA, projectB]) {
|
|
1671
|
+
const chats: Record<string, MicrosoftTeamsChat> =
|
|
1672
|
+
await storedChats(row);
|
|
1673
|
+
expect(chats).toEqual({
|
|
1674
|
+
...seedChats(),
|
|
1675
|
+
[ROSTER_GROUP_CHAT_ID]: rosterGroupChat({
|
|
1676
|
+
name: "Platform On-Call",
|
|
1677
|
+
topic: "Platform On-Call",
|
|
1678
|
+
}),
|
|
1679
|
+
[NAMED_GROUP_CHAT_ID]: namedWithoutTopic,
|
|
1680
|
+
});
|
|
1681
|
+
expect(chats[NEW_GROUP_CHAT_ID]).toBeUndefined();
|
|
1682
|
+
expect(
|
|
1683
|
+
await storedChatHasKey(row, NAMED_GROUP_CHAT_ID, "topic"),
|
|
1684
|
+
).toBe(false);
|
|
1685
|
+
await expectRestOfMiscDataKept(row);
|
|
1686
|
+
}
|
|
1687
|
+
expect(await tenantRowVersions()).toEqual([2, 2]);
|
|
1688
|
+
await expectOtherTenantUntouched();
|
|
1689
|
+
|
|
1690
|
+
// Nothing left to change: no row is written again.
|
|
1691
|
+
const before: Array<SqlRow> = await storedRows();
|
|
1692
|
+
|
|
1693
|
+
await MicrosoftTeamsUtil.renameChatsInProjectAuthTokens({
|
|
1694
|
+
tenantId: TENANT_ID,
|
|
1695
|
+
chatNames: updates,
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
expect(await storedRows()).toEqual(before);
|
|
1699
|
+
});
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
describe("removeChatFromProjectAuthTokens", () => {
|
|
1703
|
+
test("removes the chat from every row of the tenant and keeps the rest", async () => {
|
|
1704
|
+
await MicrosoftTeamsUtil.removeChatFromProjectAuthTokens({
|
|
1705
|
+
tenantId: TENANT_ID,
|
|
1706
|
+
chatId: ROSTER_GROUP_CHAT_ID,
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
const expected: Record<string, MicrosoftTeamsChat> = seedChats();
|
|
1710
|
+
delete expected[ROSTER_GROUP_CHAT_ID];
|
|
1711
|
+
|
|
1712
|
+
for (const row of [projectA, projectB]) {
|
|
1713
|
+
expect(await storedChats(row)).toEqual(expected);
|
|
1714
|
+
await expectRestOfMiscDataKept(row);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
await expectOtherTenantUntouched();
|
|
1718
|
+
});
|
|
1719
|
+
});
|
|
1720
|
+
},
|
|
1721
|
+
);
|