@oneuptime/common 14.0.8 → 14.0.9
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/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
- package/Models/DatabaseModels/Project.ts +55 -5
- package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
- package/Models/DatabaseModels/RunnerJob.ts +75 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
- 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 +207 -25
- package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- 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/Index.ts +6 -0
- package/Server/Middleware/TelemetryIngest.ts +41 -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/AutoRemediationRuleService.ts +1 -1
- 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/ProjectService.ts +32 -0
- package/Server/Services/ProxmoxResourceService.ts +322 -5
- package/Server/Services/RunnerJobService.ts +241 -104
- 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/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 +37 -15
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -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 +6 -1
- 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/MicrosoftTeams.ts +684 -47
- 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 +91 -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/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/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/RowActionPlacementOverrides.test.tsx +865 -0
- package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
- 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/KubernetesAiAgentModel.test.ts +295 -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/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/Infrastructure/GlobalCache.test.ts +195 -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/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -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/AutoRemediationRuleCommandRunners.test.ts +2 -1
- 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/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/RunnerJobEnqueueKubectl.test.ts +48 -15
- package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
- 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/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/KubectlInvestigationToolkit.test.ts +70 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
- 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/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/RemediationKubectlCopyParity.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -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/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 +45 -3
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- 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/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/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
- package/Types/AI/AIRunType.ts +9 -4
- package/Types/AI/InvestigationNotStartedReason.ts +6 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
- 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/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 +28 -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/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- package/build/dist/Models/DatabaseModels/Index.js +2 -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/Project.js +56 -5
- package/build/dist/Models/DatabaseModels/Project.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/RunnerJob.js +74 -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/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 +144 -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/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/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +34 -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/AutoRemediationRuleService.js +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.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/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.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/RunnerJobService.js +178 -92
- 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/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 +29 -15
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.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 +4 -2
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.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/MicrosoftTeams.js +505 -42
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +9 -4
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
- 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/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 +25 -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/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/Dashboard/DashboardTimeRangeZoom.js +27 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1360 @@
|
|
|
1
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
2
|
+
import { IncidentFeedEventType } from "../../../Models/DatabaseModels/IncidentFeed";
|
|
3
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
4
|
+
import MonitorStatus from "../../../Models/DatabaseModels/MonitorStatus";
|
|
5
|
+
import CustomFieldMappingService from "../../../Server/Services/CustomFieldMappingService";
|
|
6
|
+
import IncidentFeedService from "../../../Server/Services/IncidentFeedService";
|
|
7
|
+
import IncidentService from "../../../Server/Services/IncidentService";
|
|
8
|
+
import MonitorService from "../../../Server/Services/MonitorService";
|
|
9
|
+
import MonitorStatusService from "../../../Server/Services/MonitorStatusService";
|
|
10
|
+
import { OnUpdate } from "../../../Server/Types/Database/Hooks";
|
|
11
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
12
|
+
import URL from "../../../Types/API/URL";
|
|
13
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
14
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
15
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
16
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
17
|
+
import {
|
|
18
|
+
afterEach,
|
|
19
|
+
beforeEach,
|
|
20
|
+
describe,
|
|
21
|
+
expect,
|
|
22
|
+
jest,
|
|
23
|
+
test,
|
|
24
|
+
} from "@jest/globals";
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Editing an incident's Affected Resources sends its monitor list, and the
|
|
28
|
+
* status it puts its monitors in, back with every save. What that does to
|
|
29
|
+
* the monitors themselves:
|
|
30
|
+
*
|
|
31
|
+
* - a resolved incident touches no monitor. Resolving it restored its
|
|
32
|
+
* monitors, and nothing would ever undo a status (or, for a manual
|
|
33
|
+
* incident, the switch that stops probing) put on them afterwards;
|
|
34
|
+
* - an open incident puts the monitors added in its status, all of its
|
|
35
|
+
* monitors only when the edit changes that status, and restores the
|
|
36
|
+
* monitors taken off it;
|
|
37
|
+
* - a monitor taken off is restored whenever the incident was open before
|
|
38
|
+
* the update, even when the same update resolves it: resolving restores
|
|
39
|
+
* only the monitors the incident still holds;
|
|
40
|
+
* - an update that sends only the status (API, CLI, workflow) changes no
|
|
41
|
+
* membership, and must not throw;
|
|
42
|
+
* - the feed says "Monitor Status Changed" only when it did.
|
|
43
|
+
*
|
|
44
|
+
* Both hooks run for real, in order: onBeforeUpdate reads the stored
|
|
45
|
+
* incidents through a stub of findBy and hands its carry-forward to
|
|
46
|
+
* onUpdateSuccess. What the database holds after the write is served by a
|
|
47
|
+
* stub of findOneById, and whether the incident is resolved by a stub of
|
|
48
|
+
* isIncidentResolved (the stored state before the write, the state after it
|
|
49
|
+
* from then on). The monitor side effects are spies.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
const projectId: ObjectID = new ObjectID(
|
|
53
|
+
"6e1f0b2a-7c3d-4e5f-8a9b-0c1d2e3f4a01",
|
|
54
|
+
);
|
|
55
|
+
const userId: ObjectID = new ObjectID("6e1f0b2a-7c3d-4e5f-8a9b-0c1d2e3f4a02");
|
|
56
|
+
|
|
57
|
+
const INCIDENT_ID: string = "c1000000-0000-4000-8000-000000000001";
|
|
58
|
+
const SECOND_INCIDENT_ID: string = "c1000000-0000-4000-8000-000000000002";
|
|
59
|
+
|
|
60
|
+
const MONITOR_A: string = "d2000000-0000-4000-8000-00000000000a";
|
|
61
|
+
const MONITOR_B: string = "d2000000-0000-4000-8000-00000000000b";
|
|
62
|
+
const MONITOR_C: string = "d2000000-0000-4000-8000-00000000000c";
|
|
63
|
+
|
|
64
|
+
const OFFLINE: string = "e3000000-0000-4000-8000-0000000000f1";
|
|
65
|
+
const DEGRADED: string = "e3000000-0000-4000-8000-0000000000f2";
|
|
66
|
+
|
|
67
|
+
const RESOLVED_STATE: string = "f4000000-0000-4000-8000-0000000000aa";
|
|
68
|
+
// A state ordered after Resolved, so an incident in it is resolved too.
|
|
69
|
+
const CLOSED_STATE: string = "f4000000-0000-4000-8000-0000000000ab";
|
|
70
|
+
const ACKNOWLEDGED_STATE: string = "f4000000-0000-4000-8000-0000000000a2";
|
|
71
|
+
|
|
72
|
+
const HOST_ID: string = "a5000000-0000-4000-8000-000000000001";
|
|
73
|
+
|
|
74
|
+
const MONITOR_NAMES: Dictionary<string> = {
|
|
75
|
+
[MONITOR_A]: "checkout-web",
|
|
76
|
+
[MONITOR_B]: "payments-api",
|
|
77
|
+
[MONITOR_C]: "search-api",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const STATUS_NAMES: Dictionary<string> = {
|
|
81
|
+
[OFFLINE]: "Offline",
|
|
82
|
+
[DEGRADED]: "Degraded",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// An update that matches several incidents can write to up to this many.
|
|
86
|
+
const LIMIT_FOR_TEST: number = 10;
|
|
87
|
+
|
|
88
|
+
const PROPS: DatabaseCommonInteractionProps = {
|
|
89
|
+
tenantId: projectId,
|
|
90
|
+
userId: userId,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type OnBeforeUpdate = (
|
|
94
|
+
updateBy: UpdateBy<Incident>,
|
|
95
|
+
) => Promise<OnUpdate<Incident>>;
|
|
96
|
+
type OnUpdateSuccess = (
|
|
97
|
+
onUpdate: OnUpdate<Incident>,
|
|
98
|
+
updatedItemIds: Array<ObjectID>,
|
|
99
|
+
) => Promise<OnUpdate<Incident>>;
|
|
100
|
+
|
|
101
|
+
interface MonitorCarryForward {
|
|
102
|
+
monitorsRemoved: Array<Monitor>;
|
|
103
|
+
monitorsAdded: Array<Monitor>;
|
|
104
|
+
isResolvedBeforeUpdate?: boolean | undefined;
|
|
105
|
+
oldChangeMonitorStatusIdTo: ObjectID | undefined;
|
|
106
|
+
newMonitorChangeStatusIdTo: ObjectID | undefined;
|
|
107
|
+
isChangeMonitorStatusToCleared?: boolean | undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// An incident as the database holds it before the update.
|
|
111
|
+
interface StoredIncident {
|
|
112
|
+
id: string;
|
|
113
|
+
incidentNumber: number;
|
|
114
|
+
monitorIds: Array<string>;
|
|
115
|
+
changeMonitorStatusToId: string | undefined;
|
|
116
|
+
// Resolved before the update, and after it unless the update moves it.
|
|
117
|
+
isResolved: boolean;
|
|
118
|
+
isCreatedAutomatically: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function storedIncident(
|
|
122
|
+
overrides: Partial<StoredIncident> = {},
|
|
123
|
+
): StoredIncident {
|
|
124
|
+
return {
|
|
125
|
+
id: INCIDENT_ID,
|
|
126
|
+
incidentNumber: 42,
|
|
127
|
+
monitorIds: [MONITOR_A],
|
|
128
|
+
changeMonitorStatusToId: OFFLINE,
|
|
129
|
+
isResolved: false,
|
|
130
|
+
isCreatedAutomatically: false,
|
|
131
|
+
...overrides,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function monitorStub(id: string): Monitor {
|
|
136
|
+
const monitor: Monitor = new Monitor();
|
|
137
|
+
monitor._id = id;
|
|
138
|
+
return monitor;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The ids QueryHelper.any was given (it builds a Raw IN operator).
|
|
142
|
+
function idsInQueryOperator(operator: unknown): Array<string> {
|
|
143
|
+
const raw: { objectLiteralParameters?: Dictionary<unknown> } = operator as {
|
|
144
|
+
objectLiteralParameters?: Dictionary<unknown>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
Object.values(raw.objectLiteralParameters || {}) as Array<Array<unknown>>
|
|
149
|
+
)
|
|
150
|
+
.flat()
|
|
151
|
+
.map((id: unknown): string => {
|
|
152
|
+
return String(id).toLowerCase();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function lowercaseIds(ids: Array<unknown>): Array<string> {
|
|
157
|
+
return ids.map((id: unknown): string => {
|
|
158
|
+
return String(id).toLowerCase();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let storedIncidents: Array<StoredIncident> = [];
|
|
163
|
+
// The monitors each incident holds once the update is written.
|
|
164
|
+
let monitorsAfterWrite: Dictionary<Array<string>> = {};
|
|
165
|
+
// Whether each incident is resolved once the update is written.
|
|
166
|
+
let resolvedAfterWrite: Dictionary<boolean> = {};
|
|
167
|
+
|
|
168
|
+
let disableActiveMonitoringSpy: jest.SpyInstance;
|
|
169
|
+
let changeMonitorStatus: MockFunction;
|
|
170
|
+
let markMonitorsActive: MockFunction;
|
|
171
|
+
let disableActiveMonitoring: MockFunction;
|
|
172
|
+
let isIncidentResolved: MockFunction;
|
|
173
|
+
let createFeedItem: MockFunction;
|
|
174
|
+
let monitorUpdateOneById: MockFunction;
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
* Whether an incident is resolved at this point of the update: as stored
|
|
178
|
+
* until the write, as the write (or a state change after it) left it from
|
|
179
|
+
* then on.
|
|
180
|
+
*/
|
|
181
|
+
function isResolvedNow(incidentId: string): boolean {
|
|
182
|
+
if (resolvedAfterWrite[incidentId] !== undefined) {
|
|
183
|
+
return resolvedAfterWrite[incidentId] === true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const stored: StoredIncident | undefined = storedIncidents.find(
|
|
187
|
+
(incident: StoredIncident): boolean => {
|
|
188
|
+
return incident.id === incidentId;
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
return stored?.isResolved === true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* The update moves the incident to another state: changeIncidentState runs
|
|
197
|
+
* in onUpdateSuccess, and from then on the incident is resolved or not as
|
|
198
|
+
* given. The calls, and the isIncidentResolved reads around them, are
|
|
199
|
+
* recorded in order.
|
|
200
|
+
*/
|
|
201
|
+
function stubStateChange(
|
|
202
|
+
isResolvedAfterStateChange: boolean,
|
|
203
|
+
order: Array<string>,
|
|
204
|
+
): void {
|
|
205
|
+
jest
|
|
206
|
+
.spyOn(IncidentService, "changeIncidentState")
|
|
207
|
+
.mockImplementation(((data: { incidentId: ObjectID }): Promise<void> => {
|
|
208
|
+
order.push("changeIncidentState");
|
|
209
|
+
resolvedAfterWrite[data.incidentId.toString()] =
|
|
210
|
+
isResolvedAfterStateChange;
|
|
211
|
+
return Promise.resolve();
|
|
212
|
+
}) as never);
|
|
213
|
+
|
|
214
|
+
isIncidentResolved.mockImplementation(
|
|
215
|
+
(data: { incidentId: ObjectID }): Promise<boolean> => {
|
|
216
|
+
const isResolved: boolean = isResolvedNow(data.incidentId.toString());
|
|
217
|
+
order.push(`isIncidentResolved:${isResolved}`);
|
|
218
|
+
return Promise.resolve(isResolved);
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function monitorIdsOf(monitors: Array<Monitor>): Array<string> {
|
|
224
|
+
return monitors.map((monitor: Monitor): string => {
|
|
225
|
+
return monitor._id!.toString().toLowerCase();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// The monitors restored, one list per markMonitorsActiveForMonitoring call.
|
|
230
|
+
function monitorsRestored(): Array<Array<string>> {
|
|
231
|
+
return markMonitorsActive.mock.calls.map(
|
|
232
|
+
(args: Array<unknown>): Array<string> => {
|
|
233
|
+
return monitorIdsOf(args[1] as Array<Monitor>);
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
* The monitors a write leaves on an incident: the list the update sends,
|
|
240
|
+
* or the stored ones when it sends none (null clears the list).
|
|
241
|
+
*/
|
|
242
|
+
function monitorIdsWritten(
|
|
243
|
+
data: Dictionary<unknown>,
|
|
244
|
+
incident: StoredIncident,
|
|
245
|
+
): Array<string> {
|
|
246
|
+
if (data["monitors"] === undefined) {
|
|
247
|
+
return incident.monitorIds;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return ((data["monitors"] as Array<unknown> | null) || []).map(
|
|
251
|
+
(entry: unknown): string => {
|
|
252
|
+
if (typeof entry === "string") {
|
|
253
|
+
return entry.toLowerCase();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (entry instanceof ObjectID) {
|
|
257
|
+
return entry.toString().toLowerCase();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return String((entry as { _id?: unknown })._id).toLowerCase();
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function makeUpdateBy(
|
|
266
|
+
data: Dictionary<unknown>,
|
|
267
|
+
query: Dictionary<unknown> = { _id: INCIDENT_ID },
|
|
268
|
+
): UpdateBy<Incident> {
|
|
269
|
+
return {
|
|
270
|
+
query: query as UpdateBy<Incident>["query"],
|
|
271
|
+
data: data as UpdateBy<Incident>["data"],
|
|
272
|
+
props: PROPS,
|
|
273
|
+
limit: LIMIT_FOR_TEST,
|
|
274
|
+
skip: 0,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function runBeforeUpdate(
|
|
279
|
+
updateBy: UpdateBy<Incident>,
|
|
280
|
+
): Promise<OnUpdate<Incident>> {
|
|
281
|
+
return await (
|
|
282
|
+
IncidentService as unknown as { onBeforeUpdate: OnBeforeUpdate }
|
|
283
|
+
).onBeforeUpdate(updateBy);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function monitorCarryForwardOf(
|
|
287
|
+
onUpdate: OnUpdate<Incident>,
|
|
288
|
+
incidentId: string = INCIDENT_ID,
|
|
289
|
+
): MonitorCarryForward {
|
|
290
|
+
return (onUpdate.carryForward as Dictionary<MonitorCarryForward>)[
|
|
291
|
+
incidentId
|
|
292
|
+
]!;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/*
|
|
296
|
+
* One update end to end: onBeforeUpdate, the write (what findOneById and
|
|
297
|
+
* isIncidentResolved answer from then on), and onUpdateSuccess.
|
|
298
|
+
*/
|
|
299
|
+
async function runUpdate(
|
|
300
|
+
data: Dictionary<unknown>,
|
|
301
|
+
options: {
|
|
302
|
+
query?: Dictionary<unknown>;
|
|
303
|
+
monitorsAfterWrite?: Dictionary<Array<string>>;
|
|
304
|
+
} = {},
|
|
305
|
+
): Promise<OnUpdate<Incident>> {
|
|
306
|
+
const updateBy: UpdateBy<Incident> = makeUpdateBy(data, options.query);
|
|
307
|
+
|
|
308
|
+
const onUpdate: OnUpdate<Incident> = await runBeforeUpdate(updateBy);
|
|
309
|
+
|
|
310
|
+
for (const incident of storedIncidents) {
|
|
311
|
+
monitorsAfterWrite[incident.id] =
|
|
312
|
+
options.monitorsAfterWrite?.[incident.id] ||
|
|
313
|
+
monitorIdsWritten(data, incident);
|
|
314
|
+
resolvedAfterWrite[incident.id] = incident.isResolved;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await (
|
|
318
|
+
IncidentService as unknown as { onUpdateSuccess: OnUpdateSuccess }
|
|
319
|
+
).onUpdateSuccess(
|
|
320
|
+
onUpdate,
|
|
321
|
+
storedIncidents.map((incident: StoredIncident): ObjectID => {
|
|
322
|
+
return new ObjectID(incident.id);
|
|
323
|
+
}),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
return onUpdate;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// The feed item written for an incident, or undefined when none was.
|
|
330
|
+
function feedMarkdownFor(incidentId: string = INCIDENT_ID): string | undefined {
|
|
331
|
+
const call: Array<unknown> | undefined = createFeedItem.mock.calls.find(
|
|
332
|
+
(args: Array<unknown>): boolean => {
|
|
333
|
+
return (
|
|
334
|
+
String((args[0] as { incidentId: ObjectID }).incidentId) === incidentId
|
|
335
|
+
);
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (!call) {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return (call[0] as { feedInfoInMarkdown: string }).feedInfoInMarkdown;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function monitorLink(monitorId: string): string {
|
|
347
|
+
return `https://oneuptime.example/monitors/${monitorId}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// The status changes made, as [monitor ids, status id] per call.
|
|
351
|
+
function statusChanges(): Array<[Array<string>, string]> {
|
|
352
|
+
return changeMonitorStatus.mock.calls.map(
|
|
353
|
+
(args: Array<unknown>): [Array<string>, string] => {
|
|
354
|
+
return [
|
|
355
|
+
lowercaseIds(args[1] as Array<ObjectID>),
|
|
356
|
+
String(args[2]).toLowerCase(),
|
|
357
|
+
];
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
beforeEach(() => {
|
|
363
|
+
storedIncidents = [storedIncident()];
|
|
364
|
+
monitorsAfterWrite = {};
|
|
365
|
+
resolvedAfterWrite = {};
|
|
366
|
+
|
|
367
|
+
jest.spyOn(IncidentService, "findBy").mockImplementation((() => {
|
|
368
|
+
return Promise.resolve(
|
|
369
|
+
storedIncidents.map((stored: StoredIncident): Incident => {
|
|
370
|
+
const incident: Incident = new Incident();
|
|
371
|
+
incident._id = stored.id;
|
|
372
|
+
incident.projectId = projectId;
|
|
373
|
+
incident.monitors = stored.monitorIds.map(monitorStub);
|
|
374
|
+
if (stored.changeMonitorStatusToId) {
|
|
375
|
+
incident.changeMonitorStatusToId = new ObjectID(
|
|
376
|
+
stored.changeMonitorStatusToId,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return incident;
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
}) as never);
|
|
383
|
+
|
|
384
|
+
jest.spyOn(IncidentService, "findOneById").mockImplementation(((findBy: {
|
|
385
|
+
id: ObjectID;
|
|
386
|
+
}): Promise<Incident | null> => {
|
|
387
|
+
const stored: StoredIncident | undefined = storedIncidents.find(
|
|
388
|
+
(incident: StoredIncident): boolean => {
|
|
389
|
+
return incident.id === findBy.id.toString();
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (!stored) {
|
|
394
|
+
return Promise.resolve(null);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const incident: Incident = new Incident();
|
|
398
|
+
incident._id = stored.id;
|
|
399
|
+
incident.projectId = projectId;
|
|
400
|
+
incident.incidentNumber = stored.incidentNumber;
|
|
401
|
+
incident.incidentNumberWithPrefix = `INC-${stored.incidentNumber}`;
|
|
402
|
+
incident.isCreatedAutomatically = stored.isCreatedAutomatically;
|
|
403
|
+
incident.monitors = (
|
|
404
|
+
monitorsAfterWrite[stored.id] || stored.monitorIds
|
|
405
|
+
).map(monitorStub);
|
|
406
|
+
return Promise.resolve(incident);
|
|
407
|
+
}) as never);
|
|
408
|
+
|
|
409
|
+
isIncidentResolved = getJestMockFunction();
|
|
410
|
+
isIncidentResolved.mockImplementation(
|
|
411
|
+
(data: { incidentId: ObjectID }): Promise<boolean> => {
|
|
412
|
+
return Promise.resolve(isResolvedNow(data.incidentId.toString()));
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
jest
|
|
416
|
+
.spyOn(IncidentService, "isIncidentResolved")
|
|
417
|
+
.mockImplementation(isIncidentResolved as never);
|
|
418
|
+
|
|
419
|
+
markMonitorsActive = getJestMockFunction();
|
|
420
|
+
markMonitorsActive.mockResolvedValue(undefined);
|
|
421
|
+
jest
|
|
422
|
+
.spyOn(IncidentService, "markMonitorsActiveForMonitoring")
|
|
423
|
+
.mockImplementation(markMonitorsActive as never);
|
|
424
|
+
|
|
425
|
+
disableActiveMonitoring = getJestMockFunction();
|
|
426
|
+
disableActiveMonitoring.mockResolvedValue(undefined);
|
|
427
|
+
// Restored by the tests that follow the real one down to the monitors.
|
|
428
|
+
disableActiveMonitoringSpy = jest
|
|
429
|
+
.spyOn(IncidentService, "disableActiveMonitoringIfManualIncident")
|
|
430
|
+
.mockImplementation(
|
|
431
|
+
disableActiveMonitoring as never,
|
|
432
|
+
) as unknown as jest.SpyInstance;
|
|
433
|
+
|
|
434
|
+
changeMonitorStatus = getJestMockFunction();
|
|
435
|
+
changeMonitorStatus.mockResolvedValue(undefined);
|
|
436
|
+
jest
|
|
437
|
+
.spyOn(MonitorService, "changeMonitorStatus")
|
|
438
|
+
.mockImplementation(changeMonitorStatus as never);
|
|
439
|
+
|
|
440
|
+
monitorUpdateOneById = getJestMockFunction();
|
|
441
|
+
monitorUpdateOneById.mockResolvedValue(undefined);
|
|
442
|
+
jest
|
|
443
|
+
.spyOn(MonitorService, "updateOneById")
|
|
444
|
+
.mockImplementation(monitorUpdateOneById as never);
|
|
445
|
+
|
|
446
|
+
jest.spyOn(MonitorService, "findBy").mockImplementation(((findBy: {
|
|
447
|
+
query: { _id: unknown };
|
|
448
|
+
}): Promise<Array<Monitor>> => {
|
|
449
|
+
return Promise.resolve(
|
|
450
|
+
idsInQueryOperator(findBy.query._id).map((id: string): Monitor => {
|
|
451
|
+
const monitor: Monitor = monitorStub(id);
|
|
452
|
+
monitor.name = MONITOR_NAMES[id] || id;
|
|
453
|
+
return monitor;
|
|
454
|
+
}),
|
|
455
|
+
);
|
|
456
|
+
}) as never);
|
|
457
|
+
|
|
458
|
+
jest.spyOn(MonitorService, "getMonitorLinkInDashboard").mockImplementation(((
|
|
459
|
+
_projectId: ObjectID,
|
|
460
|
+
monitorId: ObjectID,
|
|
461
|
+
) => {
|
|
462
|
+
return Promise.resolve(URL.fromString(monitorLink(monitorId.toString())));
|
|
463
|
+
}) as never);
|
|
464
|
+
|
|
465
|
+
jest.spyOn(MonitorStatusService, "findOneBy").mockImplementation(((findBy: {
|
|
466
|
+
query: { _id: unknown };
|
|
467
|
+
}): Promise<MonitorStatus | null> => {
|
|
468
|
+
const id: string = String(findBy.query._id).toLowerCase();
|
|
469
|
+
|
|
470
|
+
if (!STATUS_NAMES[id]) {
|
|
471
|
+
return Promise.resolve(null);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const status: MonitorStatus = new MonitorStatus();
|
|
475
|
+
status._id = id;
|
|
476
|
+
status.name = STATUS_NAMES[id];
|
|
477
|
+
return Promise.resolve(status);
|
|
478
|
+
}) as never);
|
|
479
|
+
|
|
480
|
+
createFeedItem = getJestMockFunction();
|
|
481
|
+
createFeedItem.mockResolvedValue(undefined);
|
|
482
|
+
jest
|
|
483
|
+
.spyOn(IncidentFeedService, "createIncidentFeedItem")
|
|
484
|
+
.mockImplementation(createFeedItem as never);
|
|
485
|
+
|
|
486
|
+
jest
|
|
487
|
+
.spyOn(IncidentService, "getIncidentLinkInDashboard")
|
|
488
|
+
.mockResolvedValue(URL.fromString("https://oneuptime.example/incidents"));
|
|
489
|
+
|
|
490
|
+
jest
|
|
491
|
+
.spyOn(
|
|
492
|
+
IncidentService as unknown as {
|
|
493
|
+
validateProjectScopedReferences: () => Promise<void>;
|
|
494
|
+
},
|
|
495
|
+
"validateProjectScopedReferences",
|
|
496
|
+
)
|
|
497
|
+
.mockResolvedValue(undefined as never);
|
|
498
|
+
jest
|
|
499
|
+
.spyOn(CustomFieldMappingService, "applyMappingsToUpdate")
|
|
500
|
+
.mockResolvedValue(undefined as never);
|
|
501
|
+
jest
|
|
502
|
+
.spyOn(CustomFieldMappingService, "restampAfterMultiRowUpdate")
|
|
503
|
+
.mockReturnValue(undefined as never);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
afterEach(() => {
|
|
507
|
+
jest.restoreAllMocks();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe("IncidentService: editing the monitors of a resolved incident", () => {
|
|
511
|
+
beforeEach(() => {
|
|
512
|
+
storedIncidents = [storedIncident({ isResolved: true })];
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("adding a monitor puts no monitor back in the incident's status, and the feed still lists it", async () => {
|
|
516
|
+
await runUpdate({
|
|
517
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
518
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
522
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
523
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
524
|
+
|
|
525
|
+
const markdown: string | undefined = feedMarkdownFor();
|
|
526
|
+
|
|
527
|
+
expect(markdown).toContain(
|
|
528
|
+
`**🌎 Monitors Added**:\n- [payments-api](${monitorLink(MONITOR_B)})\n`,
|
|
529
|
+
);
|
|
530
|
+
expect(markdown).not.toContain("checkout-web");
|
|
531
|
+
expect(markdown).not.toContain("Monitor Status Changed");
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("a manual incident's added monitor is never switched off active monitoring", async () => {
|
|
535
|
+
/*
|
|
536
|
+
* The switch is only cleared when an incident resolves, so setting it on
|
|
537
|
+
* a resolved incident stops probing the monitor for good.
|
|
538
|
+
*/
|
|
539
|
+
disableActiveMonitoringSpy.mockRestore();
|
|
540
|
+
|
|
541
|
+
await runUpdate({
|
|
542
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
543
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
expect(monitorUpdateOneById).not.toHaveBeenCalled();
|
|
547
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("removing a monitor does not restore it again", async () => {
|
|
551
|
+
storedIncidents = [
|
|
552
|
+
storedIncident({ isResolved: true, monitorIds: [MONITOR_A, MONITOR_B] }),
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
await runUpdate({
|
|
556
|
+
monitors: [{ _id: MONITOR_A }],
|
|
557
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
561
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
562
|
+
expect(feedMarkdownFor()).toContain(
|
|
563
|
+
`**🗑️ Monitors Removed**:\n- [payments-api](${monitorLink(MONITOR_B)})\n`,
|
|
564
|
+
);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("changing the monitor status puts no monitor in it, and the feed records the change", async () => {
|
|
568
|
+
await runUpdate({
|
|
569
|
+
monitors: [{ _id: MONITOR_A }],
|
|
570
|
+
changeMonitorStatusTo: { _id: DEGRADED },
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
574
|
+
expect(feedMarkdownFor()).toContain(
|
|
575
|
+
"**🔄 Monitor Status Changed**:\n- **From** Offline to Degraded",
|
|
576
|
+
);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("replacing every monitor touches none of them", async () => {
|
|
580
|
+
await runUpdate({
|
|
581
|
+
monitors: [MONITOR_B, MONITOR_C],
|
|
582
|
+
changeMonitorStatusToId: DEGRADED,
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
586
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
587
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
588
|
+
|
|
589
|
+
const markdown: string | undefined = feedMarkdownFor();
|
|
590
|
+
|
|
591
|
+
expect(markdown).toContain("**🗑️ Monitors Removed**:");
|
|
592
|
+
expect(markdown).toContain("**🌎 Monitors Added**:");
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test("the incident's state is read after the update is written", async () => {
|
|
596
|
+
/*
|
|
597
|
+
* An edit that resolves the incident and adds a monitor at once: changing
|
|
598
|
+
* the state restores every monitor the incident holds then, the added
|
|
599
|
+
* one included, so the added monitor must not be put in the incident's
|
|
600
|
+
* status after that.
|
|
601
|
+
*/
|
|
602
|
+
storedIncidents = [storedIncident({ isResolved: false })];
|
|
603
|
+
|
|
604
|
+
const order: Array<string> = [];
|
|
605
|
+
|
|
606
|
+
jest
|
|
607
|
+
.spyOn(IncidentService, "changeIncidentState")
|
|
608
|
+
.mockImplementation(((data: { incidentId: ObjectID }): Promise<void> => {
|
|
609
|
+
order.push("changeIncidentState");
|
|
610
|
+
resolvedAfterWrite[data.incidentId.toString()] = true;
|
|
611
|
+
return Promise.resolve();
|
|
612
|
+
}) as never);
|
|
613
|
+
|
|
614
|
+
isIncidentResolved.mockImplementation(
|
|
615
|
+
(data: { incidentId: ObjectID }): Promise<boolean> => {
|
|
616
|
+
order.push("isIncidentResolved");
|
|
617
|
+
return Promise.resolve(
|
|
618
|
+
resolvedAfterWrite[data.incidentId.toString()] === true,
|
|
619
|
+
);
|
|
620
|
+
},
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
await runUpdate({
|
|
624
|
+
currentIncidentStateId: new ObjectID(RESOLVED_STATE),
|
|
625
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
626
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
expect(order).toEqual(["changeIncidentState", "isIncidentResolved"]);
|
|
630
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
631
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
describe("IncidentService: editing the monitors of an open incident", () => {
|
|
636
|
+
test("an added monitor is put in the incident's status, and only it", async () => {
|
|
637
|
+
await runUpdate({
|
|
638
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
639
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
expect(changeMonitorStatus).toHaveBeenCalledTimes(1);
|
|
643
|
+
|
|
644
|
+
const args: Array<unknown> = changeMonitorStatus.mock.calls[0]!;
|
|
645
|
+
|
|
646
|
+
expect(String(args[0])).toBe(projectId.toString());
|
|
647
|
+
expect(lowercaseIds(args[1] as Array<ObjectID>)).toEqual([MONITOR_B]);
|
|
648
|
+
expect(String(args[2])).toBe(OFFLINE);
|
|
649
|
+
expect(args[3]).toBe(true);
|
|
650
|
+
expect(args[4]).toBe(
|
|
651
|
+
"Status was changed because Incident INC-42 was updated.",
|
|
652
|
+
);
|
|
653
|
+
expect(args[6]).toBe(PROPS);
|
|
654
|
+
|
|
655
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(1);
|
|
656
|
+
expect(String(disableActiveMonitoring.mock.calls[0]![0])).toBe(INCIDENT_ID);
|
|
657
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
658
|
+
|
|
659
|
+
expect(feedMarkdownFor()).toContain(
|
|
660
|
+
`**🌎 Monitors Added**:\n- [payments-api](${monitorLink(MONITOR_B)})\n`,
|
|
661
|
+
);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test("a manual incident stops probing its monitors when one is added", async () => {
|
|
665
|
+
disableActiveMonitoringSpy.mockRestore();
|
|
666
|
+
|
|
667
|
+
await runUpdate({
|
|
668
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
669
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const switchedOff: Array<string> = monitorUpdateOneById.mock.calls
|
|
673
|
+
.filter((args: Array<unknown>): boolean => {
|
|
674
|
+
return (
|
|
675
|
+
(args[0] as { data: Dictionary<unknown> }).data[
|
|
676
|
+
"disableActiveMonitoringBecauseOfManualIncident"
|
|
677
|
+
] === true
|
|
678
|
+
);
|
|
679
|
+
})
|
|
680
|
+
.map((args: Array<unknown>): string => {
|
|
681
|
+
return String((args[0] as { id: ObjectID }).id).toLowerCase();
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
expect(switchedOff.sort()).toEqual([MONITOR_A, MONITOR_B]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test("an automatically created incident leaves probing on when a monitor is added", async () => {
|
|
688
|
+
storedIncidents = [storedIncident({ isCreatedAutomatically: true })];
|
|
689
|
+
|
|
690
|
+
disableActiveMonitoringSpy.mockRestore();
|
|
691
|
+
|
|
692
|
+
await runUpdate({
|
|
693
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
694
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
expect(monitorUpdateOneById).not.toHaveBeenCalled();
|
|
698
|
+
expect(statusChanges()).toEqual([[[MONITOR_B], OFFLINE]]);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test("a removed monitor is restored, and no monitor is put in the status again", async () => {
|
|
702
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
703
|
+
|
|
704
|
+
await runUpdate({
|
|
705
|
+
monitors: [{ _id: MONITOR_A }],
|
|
706
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
expect(markMonitorsActive).toHaveBeenCalledTimes(1);
|
|
710
|
+
expect(String(markMonitorsActive.mock.calls[0]![0])).toBe(
|
|
711
|
+
projectId.toString(),
|
|
712
|
+
);
|
|
713
|
+
expect(
|
|
714
|
+
lowercaseIds(
|
|
715
|
+
(markMonitorsActive.mock.calls[0]![1] as Array<Monitor>).map(
|
|
716
|
+
(monitor: Monitor): string => {
|
|
717
|
+
return monitor._id!.toString();
|
|
718
|
+
},
|
|
719
|
+
),
|
|
720
|
+
),
|
|
721
|
+
).toEqual([MONITOR_B]);
|
|
722
|
+
|
|
723
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
724
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
725
|
+
expect(feedMarkdownFor()).toContain(
|
|
726
|
+
`**🗑️ Monitors Removed**:\n- [payments-api](${monitorLink(MONITOR_B)})\n`,
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("replacing a monitor restores the old one and puts the new one in the status", async () => {
|
|
731
|
+
await runUpdate({
|
|
732
|
+
monitors: [{ _id: MONITOR_B }],
|
|
733
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
expect(
|
|
737
|
+
(markMonitorsActive.mock.calls[0]![1] as Array<Monitor>).map(
|
|
738
|
+
(monitor: Monitor): string => {
|
|
739
|
+
return monitor._id!.toString();
|
|
740
|
+
},
|
|
741
|
+
),
|
|
742
|
+
).toEqual([MONITOR_A]);
|
|
743
|
+
expect(statusChanges()).toEqual([[[MONITOR_B], OFFLINE]]);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test("saving the edit form unchanged puts no monitor in the status again", async () => {
|
|
747
|
+
/*
|
|
748
|
+
* The Affected Resources form sends the monitors and the status back
|
|
749
|
+
* with every save, here with only the hosts edited. A probe may have
|
|
750
|
+
* moved a monitor out of the incident's status since; forcing it back
|
|
751
|
+
* wrote "Status was changed because Incident #N was updated.".
|
|
752
|
+
*/
|
|
753
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
754
|
+
|
|
755
|
+
await runUpdate({
|
|
756
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
757
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
758
|
+
hosts: [{ _id: HOST_ID }],
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
762
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
763
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
764
|
+
// Nothing changed for the monitors, so there is nothing to look up.
|
|
765
|
+
expect(isIncidentResolved).not.toHaveBeenCalled();
|
|
766
|
+
// And nothing for the feed: "From Offline to Offline" is not a change.
|
|
767
|
+
expect(createFeedItem).not.toHaveBeenCalled();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("changing the monitor status puts every monitor in the new one", async () => {
|
|
771
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
772
|
+
|
|
773
|
+
await runUpdate({
|
|
774
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
775
|
+
changeMonitorStatusTo: { _id: DEGRADED },
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
expect(statusChanges()).toEqual([[[MONITOR_A, MONITOR_B], DEGRADED]]);
|
|
779
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
780
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
781
|
+
expect(feedMarkdownFor()).toContain(
|
|
782
|
+
"**🔄 Monitor Status Changed**:\n- **From** Offline to Degraded",
|
|
783
|
+
);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
test("changing the status and adding a monitor puts all of them in the new status at once", async () => {
|
|
787
|
+
await runUpdate({
|
|
788
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
789
|
+
changeMonitorStatusTo: { _id: DEGRADED },
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
expect(statusChanges()).toEqual([[[MONITOR_A, MONITOR_B], DEGRADED]]);
|
|
793
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(1);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("giving an incident a status it did not have puts every monitor in it", async () => {
|
|
797
|
+
storedIncidents = [
|
|
798
|
+
storedIncident({
|
|
799
|
+
monitorIds: [MONITOR_A, MONITOR_B],
|
|
800
|
+
changeMonitorStatusToId: undefined,
|
|
801
|
+
}),
|
|
802
|
+
];
|
|
803
|
+
|
|
804
|
+
await runUpdate({
|
|
805
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
806
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
expect(statusChanges()).toEqual([[[MONITOR_A, MONITOR_B], OFFLINE]]);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("an added monitor of an incident without a status keeps its own", async () => {
|
|
813
|
+
storedIncidents = [storedIncident({ changeMonitorStatusToId: undefined })];
|
|
814
|
+
|
|
815
|
+
await runUpdate({
|
|
816
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
820
|
+
// Probing still stops for a manual incident's monitors, as on create.
|
|
821
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(1);
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
test("clearing the status while adding a monitor puts the added monitor in no status", async () => {
|
|
825
|
+
await runUpdate({
|
|
826
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
827
|
+
changeMonitorStatusTo: null,
|
|
828
|
+
changeMonitorStatusToId: null,
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
832
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(1);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
test("the status is put only on monitors the incident holds after the write", async () => {
|
|
836
|
+
/*
|
|
837
|
+
* The monitors are read back once the update is written, so a status
|
|
838
|
+
* is never put on a monitor the write did not leave on the incident.
|
|
839
|
+
*/
|
|
840
|
+
await runUpdate(
|
|
841
|
+
{
|
|
842
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
843
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
844
|
+
},
|
|
845
|
+
{ monitorsAfterWrite: { [INCIDENT_ID]: [MONITOR_A] } },
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
describe("IncidentService: an update that changes the incident's state and its monitors at once", () => {
|
|
853
|
+
test("resolving the incident and taking a monitor off restores the monitor taken off", async () => {
|
|
854
|
+
/*
|
|
855
|
+
* Resolving restores only the monitors the incident holds after the
|
|
856
|
+
* write (IncidentStateTimelineService), so the one taken off in the same
|
|
857
|
+
* update is restored here or never: it would sit in the incident's
|
|
858
|
+
* status, and a manual incident would keep it from being probed.
|
|
859
|
+
*/
|
|
860
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
861
|
+
|
|
862
|
+
const order: Array<string> = [];
|
|
863
|
+
stubStateChange(true, order);
|
|
864
|
+
|
|
865
|
+
await runUpdate({
|
|
866
|
+
currentIncidentStateId: new ObjectID(RESOLVED_STATE),
|
|
867
|
+
monitors: [{ _id: MONITOR_A }],
|
|
868
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
expect(monitorsRestored()).toEqual([[MONITOR_B]]);
|
|
872
|
+
expect(String(markMonitorsActive.mock.calls[0]![0])).toBe(
|
|
873
|
+
projectId.toString(),
|
|
874
|
+
);
|
|
875
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
876
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
877
|
+
// The state is read before the write, while it is still open.
|
|
878
|
+
expect(order).toEqual(["isIncidentResolved:false", "changeIncidentState"]);
|
|
879
|
+
expect(feedMarkdownFor()).toContain(
|
|
880
|
+
`**🗑️ Monitors Removed**:\n- [payments-api](${monitorLink(MONITOR_B)})\n`,
|
|
881
|
+
);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("resolving the incident and taking every monitor off restores all of them", async () => {
|
|
885
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
886
|
+
|
|
887
|
+
stubStateChange(true, []);
|
|
888
|
+
|
|
889
|
+
await runUpdate({
|
|
890
|
+
currentIncidentStateId: new ObjectID(RESOLVED_STATE),
|
|
891
|
+
monitors: [],
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
expect(monitorsRestored()).toEqual([[MONITOR_A, MONITOR_B]]);
|
|
895
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("resolving the incident and replacing a monitor restores the old one and leaves the new one alone", async () => {
|
|
899
|
+
const order: Array<string> = [];
|
|
900
|
+
stubStateChange(true, order);
|
|
901
|
+
|
|
902
|
+
await runUpdate({
|
|
903
|
+
currentIncidentStateId: new ObjectID(RESOLVED_STATE),
|
|
904
|
+
monitors: [{ _id: MONITOR_B }],
|
|
905
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
expect(monitorsRestored()).toEqual([[MONITOR_A]]);
|
|
909
|
+
// Resolving restored the added monitor with the others it holds.
|
|
910
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
911
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
912
|
+
expect(order).toEqual([
|
|
913
|
+
"isIncidentResolved:false",
|
|
914
|
+
"changeIncidentState",
|
|
915
|
+
"isIncidentResolved:true",
|
|
916
|
+
]);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test("resolving the incident and changing its status restores the monitor taken off and puts none in the new status", async () => {
|
|
920
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
921
|
+
|
|
922
|
+
stubStateChange(true, []);
|
|
923
|
+
|
|
924
|
+
await runUpdate({
|
|
925
|
+
currentIncidentStateId: new ObjectID(RESOLVED_STATE),
|
|
926
|
+
monitors: [{ _id: MONITOR_A }],
|
|
927
|
+
changeMonitorStatusTo: { _id: DEGRADED },
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
expect(monitorsRestored()).toEqual([[MONITOR_B]]);
|
|
931
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
932
|
+
expect(feedMarkdownFor()).toContain("**From** Offline to Degraded");
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test("acknowledging the incident and taking a monitor off restores it as on any open incident", async () => {
|
|
936
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
937
|
+
|
|
938
|
+
stubStateChange(false, []);
|
|
939
|
+
|
|
940
|
+
await runUpdate({
|
|
941
|
+
currentIncidentStateId: new ObjectID(ACKNOWLEDGED_STATE),
|
|
942
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_C }],
|
|
943
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
expect(monitorsRestored()).toEqual([[MONITOR_B]]);
|
|
947
|
+
expect(statusChanges()).toEqual([[[MONITOR_C], OFFLINE]]);
|
|
948
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(1);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
test("reopening a resolved incident and taking a monitor off does not restore it again", async () => {
|
|
952
|
+
/*
|
|
953
|
+
* It was restored when the incident resolved. Restoring it again could
|
|
954
|
+
* overwrite a status set on it since.
|
|
955
|
+
*/
|
|
956
|
+
storedIncidents = [
|
|
957
|
+
storedIncident({ monitorIds: [MONITOR_A, MONITOR_B], isResolved: true }),
|
|
958
|
+
];
|
|
959
|
+
|
|
960
|
+
const order: Array<string> = [];
|
|
961
|
+
stubStateChange(false, order);
|
|
962
|
+
|
|
963
|
+
await runUpdate({
|
|
964
|
+
currentIncidentStateId: new ObjectID(ACKNOWLEDGED_STATE),
|
|
965
|
+
monitors: [{ _id: MONITOR_A }],
|
|
966
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
970
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
971
|
+
expect(order).toEqual(["isIncidentResolved:true", "changeIncidentState"]);
|
|
972
|
+
expect(feedMarkdownFor()).toContain("**🗑️ Monitors Removed**:");
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
test("reopening a resolved incident and adding a monitor puts the added monitor in the incident's status", async () => {
|
|
976
|
+
storedIncidents = [storedIncident({ isResolved: true })];
|
|
977
|
+
|
|
978
|
+
stubStateChange(false, []);
|
|
979
|
+
|
|
980
|
+
await runUpdate({
|
|
981
|
+
currentIncidentStateId: new ObjectID(ACKNOWLEDGED_STATE),
|
|
982
|
+
monitors: [{ _id: MONITOR_A }, { _id: MONITOR_B }],
|
|
983
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
expect(statusChanges()).toEqual([[[MONITOR_B], OFFLINE]]);
|
|
987
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(1);
|
|
988
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
test("moving a resolved incident to a later resolved state and taking a monitor off does not restore it", async () => {
|
|
992
|
+
/*
|
|
993
|
+
* The update changes the state, but the incident was resolved before it
|
|
994
|
+
* as well, so its monitors were restored already.
|
|
995
|
+
*/
|
|
996
|
+
storedIncidents = [
|
|
997
|
+
storedIncident({ monitorIds: [MONITOR_A, MONITOR_B], isResolved: true }),
|
|
998
|
+
];
|
|
999
|
+
|
|
1000
|
+
stubStateChange(true, []);
|
|
1001
|
+
|
|
1002
|
+
await runUpdate({
|
|
1003
|
+
currentIncidentStateId: new ObjectID(CLOSED_STATE),
|
|
1004
|
+
monitors: [{ _id: MONITOR_A }],
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
1008
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
test("each incident of the update restores the monitors taken off it by its own state before the update", async () => {
|
|
1012
|
+
storedIncidents = [
|
|
1013
|
+
storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] }),
|
|
1014
|
+
storedIncident({
|
|
1015
|
+
id: SECOND_INCIDENT_ID,
|
|
1016
|
+
incidentNumber: 43,
|
|
1017
|
+
monitorIds: [MONITOR_B, MONITOR_C],
|
|
1018
|
+
isResolved: true,
|
|
1019
|
+
}),
|
|
1020
|
+
];
|
|
1021
|
+
|
|
1022
|
+
stubStateChange(true, []);
|
|
1023
|
+
|
|
1024
|
+
await runUpdate(
|
|
1025
|
+
{
|
|
1026
|
+
currentIncidentStateId: new ObjectID(RESOLVED_STATE),
|
|
1027
|
+
monitors: [MONITOR_B],
|
|
1028
|
+
},
|
|
1029
|
+
{ query: { projectId: projectId } },
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
// Only the incident that was open restores the monitor taken off it.
|
|
1033
|
+
expect(monitorsRestored()).toEqual([[MONITOR_A]]);
|
|
1034
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
1035
|
+
expect(feedMarkdownFor(INCIDENT_ID)).toContain(
|
|
1036
|
+
`**🗑️ Monitors Removed**:\n- [checkout-web](${monitorLink(MONITOR_A)})\n`,
|
|
1037
|
+
);
|
|
1038
|
+
expect(feedMarkdownFor(SECOND_INCIDENT_ID)).toContain(
|
|
1039
|
+
`**🗑️ Monitors Removed**:\n- [search-api](${monitorLink(MONITOR_C)})\n`,
|
|
1040
|
+
);
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
describe("IncidentService: an update that sends the monitor status without the monitor list", () => {
|
|
1045
|
+
beforeEach(() => {
|
|
1046
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
test("does not throw, and takes no monitor off the incident", async () => {
|
|
1050
|
+
const onUpdate: OnUpdate<Incident> = await runBeforeUpdate(
|
|
1051
|
+
makeUpdateBy({ changeMonitorStatusToId: new ObjectID(DEGRADED) }),
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(onUpdate);
|
|
1055
|
+
|
|
1056
|
+
expect(carryForward.monitorsRemoved).toEqual([]);
|
|
1057
|
+
expect(carryForward.monitorsAdded).toEqual([]);
|
|
1058
|
+
expect(String(carryForward.oldChangeMonitorStatusIdTo)).toBe(OFFLINE);
|
|
1059
|
+
expect(String(carryForward.newMonitorChangeStatusIdTo)).toBe(DEGRADED);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
test("puts every monitor in the new status and restores none", async () => {
|
|
1063
|
+
await runUpdate({ changeMonitorStatusToId: new ObjectID(DEGRADED) });
|
|
1064
|
+
|
|
1065
|
+
expect(statusChanges()).toEqual([[[MONITOR_A, MONITOR_B], DEGRADED]]);
|
|
1066
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
1067
|
+
expect(disableActiveMonitoring).not.toHaveBeenCalled();
|
|
1068
|
+
expect(feedMarkdownFor()).toContain("**From** Offline to Degraded");
|
|
1069
|
+
expect(feedMarkdownFor()).not.toContain("Monitors Removed");
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
test.each([
|
|
1073
|
+
["a bare id string", { changeMonitorStatusToId: DEGRADED }],
|
|
1074
|
+
["the relation object", { changeMonitorStatusTo: { _id: DEGRADED } }],
|
|
1075
|
+
["a bare id in the relation", { changeMonitorStatusTo: DEGRADED }],
|
|
1076
|
+
] as Array<[string, Dictionary<unknown>]>)(
|
|
1077
|
+
"reads the status given as %s",
|
|
1078
|
+
async (_label: string, data: Dictionary<unknown>) => {
|
|
1079
|
+
await runUpdate(data);
|
|
1080
|
+
|
|
1081
|
+
expect(statusChanges()).toEqual([[[MONITOR_A, MONITOR_B], DEGRADED]]);
|
|
1082
|
+
},
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
test("the same status again changes nothing and writes no feed item", async () => {
|
|
1086
|
+
await runUpdate({ changeMonitorStatusToId: new ObjectID(OFFLINE) });
|
|
1087
|
+
|
|
1088
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
1089
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
1090
|
+
expect(createFeedItem).not.toHaveBeenCalled();
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
test("on a resolved incident, puts no monitor in the new status", async () => {
|
|
1094
|
+
storedIncidents = [
|
|
1095
|
+
storedIncident({ monitorIds: [MONITOR_A, MONITOR_B], isResolved: true }),
|
|
1096
|
+
];
|
|
1097
|
+
|
|
1098
|
+
await runUpdate({ changeMonitorStatusToId: new ObjectID(DEGRADED) });
|
|
1099
|
+
|
|
1100
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
1101
|
+
expect(feedMarkdownFor()).toContain("**From** Offline to Degraded");
|
|
1102
|
+
});
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
describe("IncidentService: the feed's monitor status line", () => {
|
|
1106
|
+
test("is not written when the status saved is the one the incident had", async () => {
|
|
1107
|
+
await runUpdate({
|
|
1108
|
+
title: "Checkout is slow",
|
|
1109
|
+
monitors: [{ _id: MONITOR_A }],
|
|
1110
|
+
changeMonitorStatusTo: { _id: OFFLINE },
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
const markdown: string | undefined = feedMarkdownFor();
|
|
1114
|
+
|
|
1115
|
+
// The feed item is written for the title.
|
|
1116
|
+
expect(markdown).toContain("Checkout is slow");
|
|
1117
|
+
expect(markdown).not.toContain("Monitor Status Changed");
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
test("compares the ids regardless of case", async () => {
|
|
1121
|
+
await runUpdate({
|
|
1122
|
+
monitors: [{ _id: MONITOR_A }],
|
|
1123
|
+
changeMonitorStatusToId: OFFLINE.toUpperCase(),
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
expect(changeMonitorStatus).not.toHaveBeenCalled();
|
|
1127
|
+
expect(createFeedItem).not.toHaveBeenCalled();
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
test("is written once when the status changes", async () => {
|
|
1131
|
+
await runUpdate({
|
|
1132
|
+
monitors: [{ _id: MONITOR_A }],
|
|
1133
|
+
changeMonitorStatusTo: { _id: DEGRADED },
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
const markdown: string = feedMarkdownFor()!;
|
|
1137
|
+
|
|
1138
|
+
expect(markdown.match(/Monitor Status Changed/g)).toHaveLength(1);
|
|
1139
|
+
expect(
|
|
1140
|
+
(createFeedItem.mock.calls[0]![0] as { incidentFeedEventType: string })
|
|
1141
|
+
.incidentFeedEventType,
|
|
1142
|
+
).toBe(IncidentFeedEventType.IncidentUpdated);
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
describe("IncidentService.onBeforeUpdate: which monitors an update adds and removes", () => {
|
|
1147
|
+
beforeEach(() => {
|
|
1148
|
+
storedIncidents = [storedIncident({ monitorIds: [MONITOR_A, MONITOR_B] })];
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
function addedIds(carryForward: MonitorCarryForward): Array<string> {
|
|
1152
|
+
return carryForward.monitorsAdded.map((monitor: Monitor): string => {
|
|
1153
|
+
return monitor._id!.toString().toLowerCase();
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function removedIds(carryForward: MonitorCarryForward): Array<string> {
|
|
1158
|
+
return carryForward.monitorsRemoved.map((monitor: Monitor): string => {
|
|
1159
|
+
return monitor._id!.toString().toLowerCase();
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
test.each([
|
|
1164
|
+
["relation objects", [{ _id: MONITOR_A }, { _id: MONITOR_B }]],
|
|
1165
|
+
["bare id strings (API update)", [MONITOR_A, MONITOR_B]],
|
|
1166
|
+
["ObjectIDs", [new ObjectID(MONITOR_A), new ObjectID(MONITOR_B)]],
|
|
1167
|
+
["models", [monitorStub(MONITOR_A), monitorStub(MONITOR_B)]],
|
|
1168
|
+
["upper-case ids", [MONITOR_A.toUpperCase(), MONITOR_B.toUpperCase()]],
|
|
1169
|
+
] as Array<[string, Array<unknown>]>)(
|
|
1170
|
+
"re-sending the same monitors as %s changes nothing",
|
|
1171
|
+
async (_label: string, monitors: Array<unknown>) => {
|
|
1172
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1173
|
+
await runBeforeUpdate(makeUpdateBy({ monitors: monitors })),
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
expect(removedIds(carryForward)).toEqual([]);
|
|
1177
|
+
expect(addedIds(carryForward)).toEqual([]);
|
|
1178
|
+
},
|
|
1179
|
+
);
|
|
1180
|
+
|
|
1181
|
+
test("records the monitors added and removed", async () => {
|
|
1182
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1183
|
+
await runBeforeUpdate(
|
|
1184
|
+
makeUpdateBy({ monitors: [MONITOR_B, { _id: MONITOR_C }] }),
|
|
1185
|
+
),
|
|
1186
|
+
);
|
|
1187
|
+
|
|
1188
|
+
expect(removedIds(carryForward)).toEqual([MONITOR_A]);
|
|
1189
|
+
expect(addedIds(carryForward)).toEqual([MONITOR_C]);
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
test("a monitor sent twice is added once", async () => {
|
|
1193
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1194
|
+
await runBeforeUpdate(
|
|
1195
|
+
makeUpdateBy({
|
|
1196
|
+
monitors: [MONITOR_A, MONITOR_B, MONITOR_C, { _id: MONITOR_C }],
|
|
1197
|
+
}),
|
|
1198
|
+
),
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
expect(addedIds(carryForward)).toEqual([MONITOR_C]);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
test.each([
|
|
1205
|
+
["an empty list", []],
|
|
1206
|
+
["null", null],
|
|
1207
|
+
] as Array<[string, Array<unknown> | null]>)(
|
|
1208
|
+
"%s takes every monitor off",
|
|
1209
|
+
async (_label: string, monitors: Array<unknown> | null) => {
|
|
1210
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1211
|
+
await runBeforeUpdate(makeUpdateBy({ monitors: monitors })),
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
expect(removedIds(carryForward)).toEqual([MONITOR_A, MONITOR_B]);
|
|
1215
|
+
expect(addedIds(carryForward)).toEqual([]);
|
|
1216
|
+
},
|
|
1217
|
+
);
|
|
1218
|
+
|
|
1219
|
+
test("records clearing the status", async () => {
|
|
1220
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1221
|
+
await runBeforeUpdate(
|
|
1222
|
+
makeUpdateBy({
|
|
1223
|
+
monitors: [MONITOR_A, MONITOR_B],
|
|
1224
|
+
changeMonitorStatusTo: null,
|
|
1225
|
+
}),
|
|
1226
|
+
),
|
|
1227
|
+
);
|
|
1228
|
+
|
|
1229
|
+
expect(carryForward.isChangeMonitorStatusToCleared).toBe(true);
|
|
1230
|
+
expect(carryForward.newMonitorChangeStatusIdTo).toBeUndefined();
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
test("a status sent is not a clear, even next to a null relation", async () => {
|
|
1234
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1235
|
+
await runBeforeUpdate(
|
|
1236
|
+
makeUpdateBy({
|
|
1237
|
+
changeMonitorStatusTo: null,
|
|
1238
|
+
changeMonitorStatusToId: DEGRADED,
|
|
1239
|
+
}),
|
|
1240
|
+
),
|
|
1241
|
+
);
|
|
1242
|
+
|
|
1243
|
+
expect(carryForward.isChangeMonitorStatusToCleared).toBe(false);
|
|
1244
|
+
expect(String(carryForward.newMonitorChangeStatusIdTo)).toBe(DEGRADED);
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
test.each([
|
|
1248
|
+
["open", false],
|
|
1249
|
+
["resolved", true],
|
|
1250
|
+
] as Array<[string, boolean]>)(
|
|
1251
|
+
"records that the incident was %s before an update that takes a monitor off",
|
|
1252
|
+
async (_label: string, isResolved: boolean) => {
|
|
1253
|
+
storedIncidents = [
|
|
1254
|
+
storedIncident({
|
|
1255
|
+
monitorIds: [MONITOR_A, MONITOR_B],
|
|
1256
|
+
isResolved: isResolved,
|
|
1257
|
+
}),
|
|
1258
|
+
];
|
|
1259
|
+
|
|
1260
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1261
|
+
await runBeforeUpdate(makeUpdateBy({ monitors: [MONITOR_A] })),
|
|
1262
|
+
);
|
|
1263
|
+
|
|
1264
|
+
expect(carryForward.isResolvedBeforeUpdate).toBe(isResolved);
|
|
1265
|
+
expect(isIncidentResolved).toHaveBeenCalledTimes(1);
|
|
1266
|
+
expect(
|
|
1267
|
+
String(
|
|
1268
|
+
(isIncidentResolved.mock.calls[0]![0] as { incidentId: ObjectID })
|
|
1269
|
+
.incidentId,
|
|
1270
|
+
),
|
|
1271
|
+
).toBe(INCIDENT_ID);
|
|
1272
|
+
},
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
test.each([
|
|
1276
|
+
["adds a monitor", { monitors: [MONITOR_A, MONITOR_B, MONITOR_C] }],
|
|
1277
|
+
["re-sends the same monitors", { monitors: [MONITOR_A, MONITOR_B] }],
|
|
1278
|
+
["sends only the status", { changeMonitorStatusToId: DEGRADED }],
|
|
1279
|
+
] as Array<[string, Dictionary<unknown>]>)(
|
|
1280
|
+
"does not read the state before an update that %s",
|
|
1281
|
+
async (_label: string, data: Dictionary<unknown>) => {
|
|
1282
|
+
const carryForward: MonitorCarryForward = monitorCarryForwardOf(
|
|
1283
|
+
await runBeforeUpdate(makeUpdateBy(data)),
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
expect(carryForward.isResolvedBeforeUpdate).toBeUndefined();
|
|
1287
|
+
expect(isIncidentResolved).not.toHaveBeenCalled();
|
|
1288
|
+
},
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
test("an update that touches neither reads no incident", async () => {
|
|
1292
|
+
const onUpdate: OnUpdate<Incident> = await runBeforeUpdate(
|
|
1293
|
+
makeUpdateBy({ title: "Renamed" }),
|
|
1294
|
+
);
|
|
1295
|
+
|
|
1296
|
+
expect(onUpdate.carryForward).toEqual({});
|
|
1297
|
+
expect(IncidentService.findBy).not.toHaveBeenCalled();
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
describe("IncidentService: an update that matches several incidents", () => {
|
|
1302
|
+
test("each incident puts the monitors added to it in its own status", async () => {
|
|
1303
|
+
storedIncidents = [
|
|
1304
|
+
storedIncident({ monitorIds: [MONITOR_A] }),
|
|
1305
|
+
storedIncident({
|
|
1306
|
+
id: SECOND_INCIDENT_ID,
|
|
1307
|
+
incidentNumber: 43,
|
|
1308
|
+
monitorIds: [MONITOR_B],
|
|
1309
|
+
changeMonitorStatusToId: DEGRADED,
|
|
1310
|
+
}),
|
|
1311
|
+
];
|
|
1312
|
+
|
|
1313
|
+
await runUpdate(
|
|
1314
|
+
{ monitors: [MONITOR_A, MONITOR_B] },
|
|
1315
|
+
{ query: { projectId: projectId } },
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1318
|
+
expect(statusChanges()).toEqual([
|
|
1319
|
+
[[MONITOR_B], OFFLINE],
|
|
1320
|
+
[[MONITOR_A], DEGRADED],
|
|
1321
|
+
]);
|
|
1322
|
+
expect(changeMonitorStatus.mock.calls[1]![4]).toBe(
|
|
1323
|
+
"Status was changed because Incident INC-43 was updated.",
|
|
1324
|
+
);
|
|
1325
|
+
expect(disableActiveMonitoring).toHaveBeenCalledTimes(2);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
test("a resolved one is left alone while an open one is updated", async () => {
|
|
1329
|
+
storedIncidents = [
|
|
1330
|
+
storedIncident({ monitorIds: [MONITOR_A] }),
|
|
1331
|
+
storedIncident({
|
|
1332
|
+
id: SECOND_INCIDENT_ID,
|
|
1333
|
+
incidentNumber: 43,
|
|
1334
|
+
monitorIds: [MONITOR_C],
|
|
1335
|
+
changeMonitorStatusToId: DEGRADED,
|
|
1336
|
+
isResolved: true,
|
|
1337
|
+
}),
|
|
1338
|
+
];
|
|
1339
|
+
|
|
1340
|
+
await runUpdate(
|
|
1341
|
+
{ monitors: [MONITOR_A, MONITOR_B] },
|
|
1342
|
+
{ query: { projectId: projectId } },
|
|
1343
|
+
);
|
|
1344
|
+
|
|
1345
|
+
expect(statusChanges()).toEqual([[[MONITOR_B], OFFLINE]]);
|
|
1346
|
+
expect(
|
|
1347
|
+
disableActiveMonitoring.mock.calls.map((args: Array<unknown>): string => {
|
|
1348
|
+
return String(args[0]);
|
|
1349
|
+
}),
|
|
1350
|
+
).toEqual([INCIDENT_ID]);
|
|
1351
|
+
// The resolved incident's removed monitor is not restored again.
|
|
1352
|
+
expect(markMonitorsActive).not.toHaveBeenCalled();
|
|
1353
|
+
|
|
1354
|
+
// Both feeds still record the edit.
|
|
1355
|
+
expect(feedMarkdownFor(INCIDENT_ID)).toContain("**🌎 Monitors Added**:");
|
|
1356
|
+
expect(feedMarkdownFor(SECOND_INCIDENT_ID)).toContain(
|
|
1357
|
+
`**🗑️ Monitors Removed**:\n- [search-api](${monitorLink(MONITOR_C)})\n`,
|
|
1358
|
+
);
|
|
1359
|
+
});
|
|
1360
|
+
});
|