diff --git a/packages/compass-connections-navigation/src/base-navigation-item.tsx b/packages/compass-connections-navigation/src/base-navigation-item.tsx
index 0b39b0ca02b..65c5ffa2189 100644
--- a/packages/compass-connections-navigation/src/base-navigation-item.tsx
+++ b/packages/compass-connections-navigation/src/base-navigation-item.tsx
@@ -5,12 +5,23 @@ import {
css,
ItemActionControls,
cx,
+ Badge,
+ BadgeVariant,
+ Tooltip,
+ useDarkMode,
+ Body,
} from '@mongodb-js/compass-components';
import { type Actions, ROW_HEIGHT } from './constants';
import { ExpandButton } from './tree-item';
import { type NavigationItemActions } from './item-actions';
+import type {
+ ConnectedConnectionTreeItem,
+ NotConnectedConnectionTreeItem,
+ SidebarTreeItem,
+} from './tree-data';
type NavigationBaseItemProps = {
+ item: SidebarTreeItem;
name: string;
isActive: boolean;
isExpandVisible: boolean;
@@ -86,7 +97,66 @@ const actionControlsWrapperStyles = css({
gap: spacing[100],
});
+const ClusterStateBadge: React.FunctionComponent<{
+ state: string;
+}> = ({ state }) => {
+ const badgeVariant =
+ state === 'CREATING'
+ ? BadgeVariant.Blue
+ : state === 'DELETED'
+ ? BadgeVariant.Red
+ : BadgeVariant.LightGray;
+ const badgeText =
+ state === 'DELETING'
+ ? 'TERMINATING'
+ : state === 'DELETED'
+ ? 'TERMINATED'
+ : state;
+
+ return (
+
+ {badgeText}
+
+ );
+};
+
+const ClusterStateBadgeWithTooltip: React.FunctionComponent<{
+ item: ConnectedConnectionTreeItem | NotConnectedConnectionTreeItem;
+}> = ({ item }) => {
+ const isDarkMode = useDarkMode();
+
+ const atlasClusterState = item.connectionInfo.atlasMetadata?.clusterState;
+ if (atlasClusterState === 'PAUSED') {
+ return (
+ ) => (
+
+
+ {tooltipChildren}
+
+ )}
+ >
+ Unpause your cluster to connect to it
+
+ );
+ } else if (
+ atlasClusterState === 'DELETING' ||
+ atlasClusterState === 'CREATING' ||
+ atlasClusterState === 'DELETED'
+ ) {
+ return ;
+ }
+
+ return null;
+};
+
export const NavigationBaseItem: React.FC = ({
+ item,
isActive,
actionProps,
name,
@@ -102,6 +172,7 @@ export const NavigationBaseItem: React.FC = ({
children,
}) => {
const [hoverProps, isHovered] = useHoverState();
+
return (
= ({
{icon}
{name}
+ {item.type === 'connection' && (
+
+ )}
{
return getVirtualTreeItems({
@@ -69,6 +72,13 @@ const ConnectionsNavigationTree: React.FunctionComponent<
const onDefaultAction: OnDefaultAction = useCallback(
(item, evt) => {
+ if (showDisabledConnections) {
+ const connectionId = getConnectionId(item);
+ if (!getConnectable(connectionId)) {
+ return;
+ }
+ }
+
if (item.type === 'connection') {
if (item.connectionStatus === 'connected') {
onItemAction(item, 'select-connection');
@@ -83,7 +93,7 @@ const ConnectionsNavigationTree: React.FunctionComponent<
}
}
},
- [onItemAction]
+ [onItemAction, getConnectable, showDisabledConnections]
);
const activeItemId = useMemo(() => {
@@ -144,6 +154,14 @@ const ConnectionsNavigationTree: React.FunctionComponent<
const getItemActionsAndConfig = useCallback(
(item: SidebarTreeItem) => {
+ if (showDisabledConnections) {
+ const connectionId = getConnectionId(item);
+ if (!getConnectable(connectionId)) {
+ return {
+ actions: [],
+ };
+ }
+ }
switch (item.type) {
case 'placeholder':
return {
@@ -192,7 +210,12 @@ const ConnectionsNavigationTree: React.FunctionComponent<
};
}
},
- [isRenameCollectionEnabled, getCollapseAfterForConnectedItem]
+ [
+ isRenameCollectionEnabled,
+ getCollapseAfterForConnectedItem,
+ getConnectable,
+ showDisabledConnections,
+ ]
);
const isTestEnv = process.env.NODE_ENV === 'test';
diff --git a/packages/compass-connections-navigation/src/navigation-item-icon.tsx b/packages/compass-connections-navigation/src/navigation-item-icon.tsx
index 6b5dc4ed9ae..1dd55b8b34b 100644
--- a/packages/compass-connections-navigation/src/navigation-item-icon.tsx
+++ b/packages/compass-connections-navigation/src/navigation-item-icon.tsx
@@ -63,6 +63,22 @@ export const NavigationItemIcon = ({ item }: { item: SidebarTreeItem }) => {
return ;
}
if (item.type === 'connection') {
+ const atlasClusterState = item.connectionInfo.atlasMetadata?.clusterState;
+ if (atlasClusterState === 'DELETING' || atlasClusterState === 'CREATING') {
+ return (
+
+
+
+ );
+ }
+ if (atlasClusterState === 'PAUSED' || atlasClusterState === 'DELETED') {
+ return (
+
+
+
+ );
+ }
+
const isFavorite = item.connectionInfo.savedConnectionType === 'favorite';
if (isFavorite) {
return (
diff --git a/packages/compass-connections-navigation/src/navigation-item.tsx b/packages/compass-connections-navigation/src/navigation-item.tsx
index 46b576b9576..4ba31889ce7 100644
--- a/packages/compass-connections-navigation/src/navigation-item.tsx
+++ b/packages/compass-connections-navigation/src/navigation-item.tsx
@@ -212,6 +212,7 @@ export function NavigationItem({
) : (
(isDarkMode ? palette.gray.light1 : palette.gray.dark1),
[isDarkMode]
);
+ const getConnectable = useConnectable();
const style: React.CSSProperties & AcceptedStyles = useMemo(() => {
const style: AcceptedStyles = {};
+ const connectionId = getConnectionId(item);
+ const isConnectable =
+ !showDisabledConnections || getConnectable(connectionId);
const isDisconnectedConnection =
item.type === 'connection' && item.connectionStatus !== 'connected';
const isNonExistentNamespace =
@@ -44,12 +51,12 @@ export default function StyledNavigationItem({
style['--item-bg-color-active'] = connectionColorToHexActive(colorCode);
}
- if (isDisconnectedConnection || isNonExistentNamespace) {
+ if (isDisconnectedConnection || isNonExistentNamespace || !isConnectable) {
style['--item-color'] = inactiveColor;
}
- // For a non-existent namespace, even if its active, we show it as inactive
- if (isNonExistentNamespace) {
+ // We always show these as inactive
+ if (isNonExistentNamespace || !isConnectable) {
style['--item-color-active'] = inactiveColor;
}
return style;
@@ -57,6 +64,8 @@ export default function StyledNavigationItem({
inactiveColor,
item,
colorCode,
+ getConnectable,
+ showDisabledConnections,
connectionColorToHex,
connectionColorToHexActive,
]);
diff --git a/packages/compass-connections-navigation/src/tree-data.ts b/packages/compass-connections-navigation/src/tree-data.ts
index 2d3476f2649..3d14d0eeacd 100644
--- a/packages/compass-connections-navigation/src/tree-data.ts
+++ b/packages/compass-connections-navigation/src/tree-data.ts
@@ -124,6 +124,16 @@ export type SidebarActionableItem =
export type SidebarTreeItem = PlaceholderTreeItem | SidebarActionableItem;
+export function getConnectionId(item: SidebarTreeItem): string {
+ if (item.type === 'placeholder') {
+ return '';
+ } else if (item.type === 'connection') {
+ return item.connectionInfo.id;
+ } else {
+ return item.connectionId;
+ }
+}
+
const notConnectedConnectionToItems = ({
connection: { name, connectionInfo, connectionStatus },
connectionIndex,
diff --git a/packages/compass-connections/src/hooks/use-connectable.ts b/packages/compass-connections/src/hooks/use-connectable.ts
new file mode 100644
index 00000000000..df2ead4262b
--- /dev/null
+++ b/packages/compass-connections/src/hooks/use-connectable.ts
@@ -0,0 +1,20 @@
+import { useStore } from '../stores/store-context';
+import { useCallback } from 'react';
+import { connectable } from '../utils/connection-supports';
+
+export function useConnectable(): (connectionId: string) => boolean {
+ const store = useStore();
+ const getConnectable = useCallback(
+ (connectionId: string) => {
+ const conn = store.getState().connections.byId[connectionId];
+ if (!conn) {
+ return false;
+ }
+
+ return connectable(conn.info);
+ },
+ [store]
+ );
+
+ return getConnectable;
+}
diff --git a/packages/compass-connections/src/index.tsx b/packages/compass-connections/src/index.tsx
index e6a20fabea6..aab107b17a2 100644
--- a/packages/compass-connections/src/index.tsx
+++ b/packages/compass-connections/src/index.tsx
@@ -23,7 +23,7 @@ import {
ConnectionActionsProvider,
} from './stores/store-context';
export type { ConnectionFeature } from './utils/connection-supports';
-export { connectionSupports } from './utils/connection-supports';
+export { connectionSupports, connectable } from './utils/connection-supports';
const ConnectionsComponent: React.FunctionComponent<{
/**
diff --git a/packages/compass-connections/src/provider.ts b/packages/compass-connections/src/provider.ts
index c3f97fb6168..e01a522615a 100644
--- a/packages/compass-connections/src/provider.ts
+++ b/packages/compass-connections/src/provider.ts
@@ -69,6 +69,8 @@ export type { ConnectionsService } from './stores/store-context';
export { useConnectionSupports } from './hooks/use-connection-supports';
+export { useConnectable } from './hooks/use-connectable';
+
const ConnectionStatus = {
/**
* @deprecated use a string literal directly
diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts
index c8c01277fee..e6b0f8a7c4d 100644
--- a/packages/compass-connections/src/stores/connections-store-redux.ts
+++ b/packages/compass-connections/src/stores/connections-store-redux.ts
@@ -32,6 +32,7 @@ import EventEmitter from 'events';
import { showNonGenuineMongoDBWarningModal as _showNonGenuineMongoDBWarningModal } from '../components/non-genuine-connection-modal';
import ConnectionString from 'mongodb-connection-string-url';
import type { ExtraConnectionData as ExtraConnectionDataForTelemetry } from '@mongodb-js/compass-telemetry';
+import { connectable } from '../utils/connection-supports';
export type ConnectionsEventMap = {
connected: (
@@ -530,9 +531,12 @@ function mergeConnections(
? newConnections
: [newConnections];
+ const removedConnectionIds = new Set(connectionsState.ids);
+
let newConnectionsById = connectionsState.byId;
for (const connectionInfo of newConnections) {
+ removedConnectionIds.delete(connectionInfo.id);
const existingConnection = newConnectionsById[connectionInfo.id];
// If we got a new connection, just create a default state for this
@@ -557,6 +561,30 @@ function mergeConnections(
info: connectionInfo,
},
};
+
+ // TODO(COMPASS-9319): if an Atlas connection is going from PAUSED state to unpaused, we should
+ // reconnect the data service, since it would previously have been disconnected
+ // due to non-retryable error code.
+ }
+ }
+
+ // If an Atlas connection was removed, it means that the cluster was deleted
+ for (const connectionId of removedConnectionIds) {
+ const removedConnection = newConnectionsById[connectionId];
+ if (removedConnection.info.atlasMetadata) {
+ newConnectionsById = {
+ ...newConnectionsById,
+ [connectionId]: {
+ ...removedConnection,
+ info: {
+ ...removedConnection.info,
+ atlasMetadata: {
+ ...removedConnection.info.atlasMetadata,
+ clusterState: 'DELETED',
+ },
+ },
+ },
+ };
}
}
@@ -1399,7 +1427,9 @@ function getDescriptionForNonRetryableError(error: Error): string {
// to the generic error description.
const reason = error.message.match(/code: \d+, reason: (.*)$/)?.[1];
return reason && reason.length > 0
- ? reason
+ ? reason.endsWith('.')
+ ? reason.slice(0, -1)
+ : reason // Remove trailing period
: NonRetryableErrorDescriptionFallbacks[
Number(
error.message.match(/code: (\d+),/)?.[1]
@@ -1489,6 +1519,10 @@ const connectWithOptions = (
return;
}
+ if (!connectable(connectionInfo)) {
+ return;
+ }
+
const isAutoconnectAttempt = isAutoconnectInfo(
getState(),
connectionInfo.id
diff --git a/packages/compass-connections/src/utils/connection-supports.ts b/packages/compass-connections/src/utils/connection-supports.ts
index c70388f5b68..f1767a6f606 100644
--- a/packages/compass-connections/src/utils/connection-supports.ts
+++ b/packages/compass-connections/src/utils/connection-supports.ts
@@ -11,6 +11,19 @@ function supportsRollingIndexCreation(connectionInfo: ConnectionInfo) {
return atlasMetadata.supports.rollingIndexes;
}
+export function connectable(connectionInfo: ConnectionInfo) {
+ const atlasClusterState = connectionInfo.atlasMetadata?.clusterState;
+ if (
+ atlasClusterState === 'DELETED' ||
+ atlasClusterState === 'DELETING' ||
+ atlasClusterState === 'CREATING' ||
+ atlasClusterState === 'PAUSED'
+ ) {
+ return false;
+ }
+ return true;
+}
+
function supportsGlobalWrites(connectionInfo: ConnectionInfo) {
const atlasMetadata = connectionInfo.atlasMetadata;
diff --git a/packages/compass-preferences-model/src/feature-flags.ts b/packages/compass-preferences-model/src/feature-flags.ts
index f16ed69ed35..668556e7286 100644
--- a/packages/compass-preferences-model/src/feature-flags.ts
+++ b/packages/compass-preferences-model/src/feature-flags.ts
@@ -22,6 +22,7 @@ export type FeatureFlags = {
enableQueryHistoryAutocomplete: boolean;
enableProxySupport: boolean;
enableRollingIndexes: boolean;
+ showDisabledConnections: boolean;
enableGlobalWrites: boolean;
enableDataModeling: boolean;
enableIndexesGuidanceExp: boolean;
@@ -91,6 +92,14 @@ export const featureFlags: Required<{
},
},
+ showDisabledConnections: {
+ stage: 'development',
+ description: {
+ short:
+ 'Show clusters that are not in a "connectable" state in Atlas Cloud',
+ },
+ },
+
enableRollingIndexes: {
stage: 'development',
description: {
diff --git a/packages/compass-web/sandbox/index.tsx b/packages/compass-web/sandbox/index.tsx
index 1392df7a318..35dc4780dcb 100644
--- a/packages/compass-web/sandbox/index.tsx
+++ b/packages/compass-web/sandbox/index.tsx
@@ -135,6 +135,7 @@ const App = () => {
enableCreatingNewConnections: !isAtlas,
enableGlobalWrites: isAtlas,
enableRollingIndexes: isAtlas,
+ showDisabledConnections: true,
enableGenAIFeaturesAtlasProject:
isAtlas && !!enableGenAIFeaturesAtlasProject,
enableGenAISampleDocumentPassingOnAtlasProject:
diff --git a/packages/compass-web/src/connection-storage.tsx b/packages/compass-web/src/connection-storage.tsx
index a51889c2275..64265b8cc2a 100644
--- a/packages/compass-web/src/connection-storage.tsx
+++ b/packages/compass-web/src/connection-storage.tsx
@@ -247,10 +247,14 @@ export function buildConnectionInfoFromClusterDescription(
};
}
-const CONNECTABLE_CLUSTER_STATES: AtlasClusterMetadata['clusterState'][] = [
+const VISIBLE_CLUSTER_STATES: AtlasClusterMetadata['clusterState'][] = [
'IDLE',
- 'REPARING',
+ 'REPAIRING',
'UPDATING',
+ 'PAUSED',
+ 'CREATING',
+ 'DELETING',
+ 'DELETED',
];
/**
@@ -291,11 +295,8 @@ export class AtlasCloudConnectionStorage
return connectionInfoList
.map((connectionInfo: ConnectionInfo): ConnectionInfo | null => {
if (
- !connectionInfo.connectionOptions.connectionString ||
!connectionInfo.atlasMetadata ||
- // TODO(COMPASS-8228): do not filter out those connections, display
- // them in navigation, but in a way that doesn't allow connecting
- !CONNECTABLE_CLUSTER_STATES.includes(
+ !VISIBLE_CLUSTER_STATES.includes(
connectionInfo.atlasMetadata.clusterState
)
) {
diff --git a/packages/connection-info/src/connection-info.ts b/packages/connection-info/src/connection-info.ts
index 310bd8f237f..831bdc31d52 100644
--- a/packages/connection-info/src/connection-info.ts
+++ b/packages/connection-info/src/connection-info.ts
@@ -48,7 +48,7 @@ export interface AtlasClusterMetadata {
| 'UPDATING'
| 'PAUSED'
| 'IDLE'
- | 'REPARING'
+ | 'REPAIRING'
| 'DELETING'
| 'DELETED';