Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 (
<Badge variant={badgeVariant} data-testid="navigation-item-state-badge">
{badgeText}
</Badge>
);
};

const ClusterStateBadgeWithTooltip: React.FunctionComponent<{
item: ConnectedConnectionTreeItem | NotConnectedConnectionTreeItem;
}> = ({ item }) => {
const isDarkMode = useDarkMode();

const atlasClusterState = item.connectionInfo.atlasMetadata?.clusterState;
if (atlasClusterState === 'PAUSED') {
return (
<Tooltip
enabled={true}
darkMode={isDarkMode}
trigger={({
children: tooltipChildren,
...tooltipTriggerProps
}: React.HTMLProps<HTMLDivElement>) => (
<div {...tooltipTriggerProps}>
<ClusterStateBadge state={atlasClusterState} />
{tooltipChildren}
</div>
)}
>
<Body>Unpause your cluster to connect to it</Body>
</Tooltip>
);
} else if (
atlasClusterState === 'DELETING' ||
atlasClusterState === 'CREATING' ||
atlasClusterState === 'DELETED'
) {
return <ClusterStateBadge state={atlasClusterState} />;
}

return null;
};

export const NavigationBaseItem: React.FC<NavigationBaseItemProps> = ({
item,
isActive,
actionProps,
name,
Expand All @@ -102,6 +172,7 @@ export const NavigationBaseItem: React.FC<NavigationBaseItemProps> = ({
children,
}) => {
const [hoverProps, isHovered] = useHoverState();

return (
<div
data-testid="base-navigation-item"
Expand All @@ -127,6 +198,9 @@ export const NavigationBaseItem: React.FC<NavigationBaseItemProps> = ({
{icon}
<span title={name}>{name}</span>
</div>
{item.type === 'connection' && (
<ClusterStateBadgeWithTooltip item={item} />
)}
<div className={actionControlsWrapperStyles}>
<ItemActionControls
menuClassName={menuStyles}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useMemo } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import { getVirtualTreeItems } from './tree-data';
import { getConnectionId, getVirtualTreeItems } from './tree-data';
import { ROW_HEIGHT } from './constants';
import type { Actions } from './constants';
import { VirtualTree } from './virtual-list/virtual-list';
Expand All @@ -18,6 +18,7 @@ import {
spacing,
useId,
} from '@mongodb-js/compass-components';
import { useConnectableRef } from '@mongodb-js/compass-connections/provider';
import type { WorkspaceTab } from '@mongodb-js/compass-workspaces';
import { usePreference } from 'compass-preferences-model/provider';
import type { NavigationItemActions } from './item-actions';
Expand Down Expand Up @@ -57,6 +58,7 @@ const ConnectionsNavigationTree: React.FunctionComponent<
);

const id = useId();
const { getConnectable } = useConnectableRef();

const treeData = useMemo(() => {
return getVirtualTreeItems({
Expand All @@ -69,6 +71,11 @@ const ConnectionsNavigationTree: React.FunctionComponent<

const onDefaultAction: OnDefaultAction<SidebarActionableItem> = useCallback(
(item, evt) => {
const connectionId = getConnectionId(item);
if (!getConnectable(connectionId)) {
return;
}

if (item.type === 'connection') {
if (item.connectionStatus === 'connected') {
onItemAction(item, 'select-connection');
Expand All @@ -83,7 +90,7 @@ const ConnectionsNavigationTree: React.FunctionComponent<
}
}
},
[onItemAction]
[onItemAction, getConnectable]
);

const activeItemId = useMemo(() => {
Expand Down Expand Up @@ -144,6 +151,12 @@ const ConnectionsNavigationTree: React.FunctionComponent<

const getItemActionsAndConfig = useCallback(
(item: SidebarTreeItem) => {
const connectionId = getConnectionId(item);
if (!getConnectable(connectionId)) {
return {
actions: [],
};
}
switch (item.type) {
case 'placeholder':
return {
Expand Down Expand Up @@ -192,7 +205,11 @@ const ConnectionsNavigationTree: React.FunctionComponent<
};
}
},
[isRenameCollectionEnabled, getCollapseAfterForConnectedItem]
[
isRenameCollectionEnabled,
getCollapseAfterForConnectedItem,
getConnectable,
]
);

const isTestEnv = process.env.NODE_ENV === 'test';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ export const NavigationItemIcon = ({ item }: { item: SidebarTreeItem }) => {
return <Icon glyph="TimeSeries" />;
}
if (item.type === 'connection') {
const atlasClusterState = item.connectionInfo.atlasMetadata?.clusterState;
if (atlasClusterState === 'DELETING' || atlasClusterState === 'CREATING') {
return (
<WithStatusMarker status={'disconnected'}>
<Icon glyph="Refresh" />
</WithStatusMarker>
);
}
if (atlasClusterState === 'PAUSED' || atlasClusterState === 'DELETED') {
return (
<WithStatusMarker status={'disconnected'}>
<ServerIcon />
</WithStatusMarker>
);
}

const isFavorite = item.connectionInfo.savedConnectionType === 'favorite';
if (isFavorite) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export function NavigationItem({
<PlaceholderItem level={item.level} />
) : (
<NavigationBaseItem
item={item}
isActive={isActive}
isFocused={isFocused}
isExpanded={!!item.isExpanded}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
DefaultColorCode,
} from '@mongodb-js/connection-form';
import { palette, useDarkMode } from '@mongodb-js/compass-components';
import type { SidebarTreeItem } from './tree-data';
import { getConnectionId, type SidebarTreeItem } from './tree-data';
import { useConnectable } from '@mongodb-js/compass-connections/provider';

type AcceptedStyles = {
'--item-bg-color'?: string;
Expand All @@ -30,6 +31,9 @@ export default function StyledNavigationItem({
[isDarkMode]
);

const connectionId = getConnectionId(item);
const isConnectable = useConnectable(connectionId);

const style: React.CSSProperties & AcceptedStyles = useMemo(() => {
const style: AcceptedStyles = {};
const isDisconnectedConnection =
Expand All @@ -44,17 +48,18 @@ 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;
}, [
inactiveColor,
isConnectable,
item,
colorCode,
connectionColorToHex,
Expand Down
10 changes: 10 additions & 0 deletions packages/compass-connections-navigation/src/tree-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 34 additions & 0 deletions packages/compass-connections/src/hooks/use-connectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useSelector, useStore } from '../stores/store-context';
import { useRef, useState } from 'react';
import { connectable } from '../utils/connection-supports';

export function useConnectable(connectionId: string): boolean {
return useSelector((state) => {
const connection = state.connections.byId[connectionId];

if (!connection) {
return false;
}

return connectable(connection.info);
});
}

export function useConnectableRef(): {
getConnectable(this: void, connectionId: string): boolean;
} {
const storeRef = useRef(useStore());
const [ref] = useState(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be a cleaner way to write this?

  const store = useStore();
  const getConnectable = useCallback((connectionId: string) => {
    // ...
  }, [store]);

Fine as is too I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, would that have the same outcome?

return {
getConnectable(connectionId: string) {
const conn = storeRef.current.getState().connections.byId[connectionId];
if (!conn) {
return false;
}

return connectable(conn.info);
},
};
});
return ref;
}
2 changes: 1 addition & 1 deletion packages/compass-connections/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
/**
Expand Down
2 changes: 2 additions & 0 deletions packages/compass-connections/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export type { ConnectionsService } from './stores/store-context';

export { useConnectionSupports } from './hooks/use-connection-supports';

export { useConnectable, useConnectableRef } from './hooks/use-connectable';

const ConnectionStatus = {
/**
* @deprecated use a string literal directly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -560,6 +564,26 @@ function mergeConnections(
}
}

// 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',
},
},
},
};
}
}

// If we haven't modified newConnectionsById at this point, we can stop: none
// of the new connections are different from what we have in the state already
if (newConnectionsById === connectionsState.byId) {
Expand Down Expand Up @@ -1489,6 +1513,10 @@ const connectWithOptions = (
return;
}

if (!connectable(connectionInfo)) {
return;
}

const isAutoconnectAttempt = isAutoconnectInfo(
getState(),
connectionInfo.id
Expand Down
Loading
Loading