Skip to content

Commit df40eed

Browse files
feat(sidebar): Add the shell icon on hover to the connections in the sidebar COMPASS-8050 COMPASS-8071 (#6057)
* chore: adds shell icon on hover of connected tree item * update tests for the shell icon in the sidebar * connectionName * fix unit tests --------- Co-authored-by: Himanshu Singh <[email protected]>
1 parent 55a5a60 commit df40eed

File tree

13 files changed

+302
-115
lines changed

13 files changed

+302
-115
lines changed

packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -393,16 +393,13 @@ describe('ConnectionsNavigationTree', function () {
393393
const connection = screen.getByTestId('connection_ready');
394394

395395
expect(within(connection).getByTitle('Create database')).to.be.visible;
396+
expect(within(connection).getByTitle('Open MongoDB shell')).to.be.visible;
396397

397398
const otherActions = within(connection).getByTitle('Show actions');
398399
expect(otherActions).to.exist;
399400

400401
userEvent.click(otherActions);
401402

402-
expect(screen.getByText('Open MongoDB shell')).to.be.visible;
403-
expect(
404-
screen.getByTestId('sidebar-navigation-item-actions-open-shell-action')
405-
).not.to.have.attribute('disabled');
406403
expect(screen.getByText('View performance metrics')).to.be.visible;
407404
expect(screen.getByText('Show connection info')).to.be.visible;
408405
expect(screen.getByText('Copy connection string')).to.be.visible;
@@ -565,29 +562,19 @@ describe('ConnectionsNavigationTree', function () {
565562
const connection = screen.getByTestId('connection_ready');
566563

567564
expect(within(connection).queryByTitle('Create database')).not.to.exist;
565+
if (name !== 'when preferences is readonly') {
566+
expect(within(connection).getByLabelText('Open MongoDB shell')).to.be
567+
.visible;
568+
} else {
569+
expect(within(connection).queryByLabelText('Open MongoDB shell')).not
570+
.to.exist;
571+
}
568572

569573
const otherActions = within(connection).getByTitle('Show actions');
570574
expect(otherActions).to.exist;
571575

572576
userEvent.click(otherActions);
573577

574-
expect(screen.getByText('Open MongoDB shell')).to.be.visible;
575-
if (
576-
name !== 'when connection is datalake' &&
577-
name !== 'when connection is not writable'
578-
) {
579-
expect(
580-
screen.getByTestId(
581-
'sidebar-navigation-item-actions-open-shell-action'
582-
)
583-
).to.have.attribute('disabled');
584-
} else {
585-
expect(
586-
screen.getByTestId(
587-
'sidebar-navigation-item-actions-open-shell-action'
588-
)
589-
).not.to.have.attribute('disabled');
590-
}
591578
expect(screen.getByText('View performance metrics')).to.be.visible;
592579
expect(screen.getByText('Show connection info')).to.be.visible;
593580
expect(screen.getByText('Copy connection string')).to.be.visible;
@@ -632,6 +619,40 @@ describe('ConnectionsNavigationTree', function () {
632619
});
633620
});
634621

622+
describe('shell action', function () {
623+
it('should show shell action in the sidebar on hover of connected item', async function () {
624+
await renderConnectionsNavigationTree();
625+
userEvent.hover(screen.getByText('turtles'));
626+
expect(screen.getByLabelText('Open MongoDB shell')).to.be.visible;
627+
});
628+
629+
context('when preferences is readonly', function () {
630+
it('should not render shell action at all', async function () {
631+
await renderConnectionsNavigationTree(
632+
{},
633+
{
634+
readOnly: true,
635+
}
636+
);
637+
userEvent.hover(screen.getByText('turtles'));
638+
expect(() => screen.getByLabelText('Open MongoDB shell')).to.throw;
639+
});
640+
});
641+
642+
context('when shell is disabled', function () {
643+
it('should not render shell action at all', async function () {
644+
await renderConnectionsNavigationTree(
645+
{},
646+
{
647+
enableShell: false,
648+
}
649+
);
650+
userEvent.hover(screen.getByText('turtles'));
651+
expect(() => screen.getByLabelText('Open MongoDB shell')).to.throw;
652+
});
653+
});
654+
});
655+
635656
describe('onItemAction', function () {
636657
let preferences: PreferencesAccess;
637658
beforeEach(async function () {

packages/compass-connections-navigation/src/connections-navigation-tree.tsx

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
SidebarActionableItem,
1212
Connection,
1313
} from './tree-data';
14+
import type { ItemAction, ItemSeparator } from '@mongodb-js/compass-components';
1415
import {
1516
VisuallyHidden,
1617
css,
@@ -19,6 +20,7 @@ import {
1920
} from '@mongodb-js/compass-components';
2021
import type { WorkspaceTab } from '@mongodb-js/compass-workspaces';
2122
import { usePreference } from 'compass-preferences-model/provider';
23+
import type { NavigationItemActions } from './item-actions';
2224
import {
2325
collectionItemActions,
2426
connectedConnectionItemActions,
@@ -56,6 +58,7 @@ const ConnectionsNavigationTree: React.FunctionComponent<
5658
onItemExpand,
5759
onItemAction,
5860
}) => {
61+
const preferencesShellEnabled = usePreference('enableShell');
5962
const preferencesReadOnly = usePreference('readOnly');
6063
const isSingleConnection = !usePreference(
6164
'enableNewMultipleConnectionSystem'
@@ -72,8 +75,15 @@ const ConnectionsNavigationTree: React.FunctionComponent<
7275
isSingleConnection,
7376
expandedItems: expanded,
7477
preferencesReadOnly,
78+
preferencesShellEnabled,
7579
});
76-
}, [connections, isSingleConnection, expanded, preferencesReadOnly]);
80+
}, [
81+
connections,
82+
isSingleConnection,
83+
expanded,
84+
preferencesReadOnly,
85+
preferencesShellEnabled,
86+
]);
7787

7888
const onDefaultAction: OnDefaultAction<SidebarActionableItem> = useCallback(
7989
(item, evt) => {
@@ -115,38 +125,95 @@ const ConnectionsNavigationTree: React.FunctionComponent<
115125
}
116126
}, [activeWorkspace, isSingleConnection]);
117127

118-
const getItemActions = useCallback(
128+
const getCollapseAfterForConnectedItem = useCallback(
129+
(actions: NavigationItemActions) => {
130+
const [firstAction, secondAction] = actions;
131+
132+
const actionCanBeShownInline = (
133+
action: NavigationItemActions[number]
134+
): boolean => {
135+
if (typeof (action as ItemSeparator).separator !== 'undefined') {
136+
return false;
137+
}
138+
139+
return ['create-database', 'open-shell'].includes(
140+
(action as ItemAction<Actions>).action
141+
);
142+
};
143+
144+
// this is the normal case for a connection that is writable and when we
145+
// also have shell enabled
146+
if (
147+
actionCanBeShownInline(firstAction) &&
148+
actionCanBeShownInline(secondAction)
149+
) {
150+
return 2;
151+
}
152+
153+
// this will happen when the either the connection is not writable or the
154+
// preference is readonly, or shell is not enabled in which case we either
155+
// do not show create-database action or open-shell action
156+
if (
157+
actionCanBeShownInline(firstAction) ||
158+
actionCanBeShownInline(secondAction)
159+
) {
160+
return 1;
161+
}
162+
163+
return 0;
164+
},
165+
[]
166+
);
167+
168+
const getItemActionsAndConfig = useCallback(
119169
(item: SidebarTreeItem) => {
120170
switch (item.type) {
121171
case 'placeholder':
122-
return [];
172+
return {
173+
actions: [],
174+
};
123175
case 'connection': {
124176
if (item.connectionStatus === ConnectionStatus.Connected) {
125-
return connectedConnectionItemActions({
177+
const actions = connectedConnectionItemActions({
126178
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
127179
isShellEnabled: item.isShellEnabled,
128180
connectionInfo: item.connectionInfo,
129181
isPerformanceTabSupported: item.isPerformanceTabSupported,
130182
});
183+
return {
184+
actions: actions,
185+
config: {
186+
collapseAfter: getCollapseAfterForConnectedItem(actions),
187+
},
188+
};
131189
} else {
132-
return notConnectedConnectionItemActions({
133-
connectionInfo: item.connectionInfo,
134-
});
190+
return {
191+
actions: notConnectedConnectionItemActions({
192+
connectionInfo: item.connectionInfo,
193+
}),
194+
config: {
195+
collapseAfter: 0,
196+
},
197+
};
135198
}
136199
}
137200
case 'database':
138-
return databaseItemActions({
139-
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
140-
});
201+
return {
202+
actions: databaseItemActions({
203+
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
204+
}),
205+
};
141206
default:
142-
return collectionItemActions({
143-
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
144-
type: item.type,
145-
isRenameCollectionEnabled,
146-
});
207+
return {
208+
actions: collectionItemActions({
209+
hasWriteActionsDisabled: item.hasWriteActionsDisabled,
210+
type: item.type,
211+
isRenameCollectionEnabled,
212+
}),
213+
};
147214
}
148215
},
149-
[isRenameCollectionEnabled]
216+
[isRenameCollectionEnabled, getCollapseAfterForConnectedItem]
150217
);
151218

152219
const isTestEnv = process.env.NODE_ENV === 'test';
@@ -166,7 +233,7 @@ const ConnectionsNavigationTree: React.FunctionComponent<
166233
onDefaultAction={onDefaultAction}
167234
onItemAction={onItemAction}
168235
onItemExpand={onItemExpand}
169-
getItemActions={getItemActions}
236+
getItemActions={getItemActionsAndConfig}
170237
getItemKey={(item) => item.id}
171238
renderItem={({
172239
item,

packages/compass-connections-navigation/src/item-actions.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,8 @@ export const connectedConnectionItemActions = ({
6262
connectionInfo,
6363
isEditDisabled: true,
6464
}).slice(1); // for connected connections we don't show connect action
65+
6566
const actions: NavigationItemActions = [
66-
{
67-
action: 'create-database',
68-
icon: 'Plus',
69-
label: 'Create database',
70-
},
71-
{
72-
action: 'open-shell',
73-
icon: 'Shell',
74-
label: 'Open MongoDB shell',
75-
isDisabled: !isShellEnabled,
76-
disabledDescription: 'Not available',
77-
},
7867
{
7968
action: 'connection-performance-metrics',
8069
icon: 'Gauge',
@@ -102,11 +91,25 @@ export const connectedConnectionItemActions = ({
10291
...connectionManagementActions,
10392
];
10493

94+
// we show shell icon only when its enabled
95+
if (isShellEnabled) {
96+
actions.unshift({
97+
action: 'open-shell',
98+
icon: 'Shell',
99+
label: 'Open MongoDB shell',
100+
});
101+
}
102+
105103
// when connection is readonly we don't want to show create-database action
106104
// and hence we splice it out here
107-
if (hasWriteActionsDisabled) {
108-
actions.splice(0, 1);
105+
if (!hasWriteActionsDisabled) {
106+
actions.unshift({
107+
action: 'create-database',
108+
icon: 'Plus',
109+
label: 'Create database',
110+
});
109111
}
112+
110113
return actions;
111114
};
112115

packages/compass-connections-navigation/src/navigation-item.tsx

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ type NavigationItemProps = {
9696
item: SidebarTreeItem;
9797
isActive: boolean;
9898
isFocused: boolean;
99-
getItemActions: (item: SidebarTreeItem) => NavigationItemActions;
99+
getItemActions: (item: SidebarTreeItem) => {
100+
actions: NavigationItemActions;
101+
config?: {
102+
collapseAfter: number;
103+
};
104+
};
100105
onItemAction: (item: SidebarActionableItem, action: Actions) => void;
101106
onItemExpand(item: SidebarActionableItem, isExpanded: boolean): void;
102107
};
@@ -159,25 +164,13 @@ export function NavigationItem({
159164
const style = useMemo(() => getTreeItemStyles(item), [item]);
160165

161166
const actionProps = useMemo(() => {
162-
const collapseAfter = (() => {
163-
if (item.type === 'connection') {
164-
if (
165-
item.connectionStatus === ConnectionStatus.Connected &&
166-
!item.hasWriteActionsDisabled
167-
) {
168-
return 1;
169-
}
170-
// when connected connection is readonly we don't show the create-database action
171-
// so the whole action menu is collapsed
172-
return 0;
173-
}
174-
})();
167+
const { actions, config: actionsConfig } = getItemActions(item);
175168

176169
return {
177-
actions: getItemActions(item),
170+
actions: actions,
178171
onAction: onAction,
179-
...(typeof collapseAfter === 'number' && {
180-
collapseAfter,
172+
...(typeof actionsConfig?.collapseAfter === 'number' && {
173+
collapseAfter: actionsConfig?.collapseAfter,
181174
}),
182175
...(item.type === 'database' && {
183176
collapseToMenuThreshold: 3,

0 commit comments

Comments
 (0)