Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const connections: Connection[] = [
const props: React.ComponentProps<typeof ConnectionsNavigationTree> = {
connections,
expanded: { turtles: { bar: true } },
expandedGroups: {},
activeWorkspace: {
connectionId: 'connection_ready',
namespace: 'db_ready.meow',
Expand Down Expand Up @@ -161,6 +162,155 @@ describe('ConnectionsNavigationTree', function () {

afterEach(cleanup);

context('with connection groups enabled', function () {
const groupedConnections: Connection[] = [
{
...connections[0],
connectionInfo: {
...connections[0].connectionInfo,
favorite: { name: 'turtles', groupId: 'prod' },
},
},
connections[2], // disconnected, no group
];

it('renders a group header and indents grouped connections', async function () {
await renderConnectionsNavigationTree(
{ connections: groupedConnections, expandedGroups: {} },
{ enableConnectionGroups: true }
);
const group = screen.getByTestId('group:prod');
expect(group).to.exist;
expect(within(group).getByText('prod')).to.exist;
expect(screen.getByText('turtles')).to.exist;
});

it('does not render group headers when the flag is disabled', async function () {
await renderConnectionsNavigationTree({
connections: groupedConnections,
expandedGroups: {},
});
expect(screen.queryByTestId('group:prod')).to.be.null;
});

it('resolves the group header name and color from the groupsById prop', async function () {
await renderConnectionsNavigationTree(
{
connections: groupedConnections,
expandedGroups: {},
groupsById: {
prod: { id: 'prod', name: 'Production', color: 'color1' },
},
},
{ enableConnectionGroups: true }
);
const group = screen.getByTestId('group:prod');
expect(within(group).getByText('Production')).to.exist;
expect(within(group).queryByText('prod')).to.be.null;
});

it('notifies on group expand toggle', async function () {
const spy = Sinon.spy();
await renderConnectionsNavigationTree(
{
connections: groupedConnections,
expandedGroups: {},
onItemExpand: spy,
},
{ enableConnectionGroups: true }
);
const group = screen.getByTestId('group:prod');
userEvent.click(within(group).getByLabelText('Collapse'));
expect(spy).to.be.calledOnce;
const [[item, isExpanded]] = spy.args;
expect(item.type).to.equal('group');
expect(item.groupName).to.equal('prod');
expect(isExpanded).to.equal(false);
});

it('toggles the group from its row default action', async function () {
const spy = Sinon.spy();
await renderConnectionsNavigationTree(
{
connections: groupedConnections,
expandedGroups: {},
onItemExpand: spy,
},
{ enableConnectionGroups: true }
);
const group = screen.getByTestId('group:prod');

// Clicking the row (not the Collapse chevron) routes through
// onDefaultAction, which must not be blocked by the disabled-connection
// guard for group items.
userEvent.click(within(group).getByText('prod'));

expect(spy).to.be.calledOnce;
const [[item, isExpanded]] = spy.args;
expect(item.type).to.equal('group');
expect(item.groupName).to.equal('prod');
expect(isExpanded).to.equal(false);
});

it('hides connections of a collapsed group', async function () {
await renderConnectionsNavigationTree(
{
connections: groupedConnections,
expandedGroups: { prod: false },
},
{ enableConnectionGroups: true }
);
expect(screen.getByTestId('group:prod')).to.exist;
expect(screen.queryByText('turtles')).to.be.null;
});

it('fires edit-group from the group actions', async function () {
const spy = Sinon.spy();
await renderConnectionsNavigationTree(
{
connections: groupedConnections,
expandedGroups: {},
onItemAction: spy,
},
{ enableConnectionGroups: true }
);
const group = screen.getByTestId('group:prod');

userEvent.hover(within(group).getByText('prod'));
userEvent.click(within(group).getByTitle('Show actions'));
userEvent.click(screen.getByText('Edit group'));

expect(spy).to.be.calledOnce;
const [[item, action]] = spy.args;
expect(item.type).to.equal('group');
expect(item.groupName).to.equal('prod');
expect(action).to.equal('edit-group');
});

it('fires delete-group from the group actions', async function () {
const spy = Sinon.spy();
await renderConnectionsNavigationTree(
{
connections: groupedConnections,
expandedGroups: {},
onItemAction: spy,
},
{ enableConnectionGroups: true }
);
const group = screen.getByTestId('group:prod');

userEvent.hover(within(group).getByText('prod'));
userEvent.click(within(group).getByTitle('Show actions'));
userEvent.click(screen.getByText('Delete group'));

expect(spy).to.be.calledOnce;
const [[item, action]] = spy.args;
expect(item.type).to.equal('group');
expect(item.groupName).to.equal('prod');
expect(action).to.equal('delete-group');
});
});

context('when the rename collection feature flag is enabled', () => {
it('shows the Rename Collection action', async function () {
await renderConnectionsNavigationTree({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
databaseContextMenuActions,
notConnectedConnectionItemActions,
connectionContextMenuActions,
groupItemActions,
groupContextMenuActions,
} from './item-actions';
import { itemActionsToContextMenuGroups } from './context-menus';

Expand All @@ -43,6 +45,8 @@ export interface ConnectionsNavigationTreeProps {
connections: Connection[];
activeWorkspace: WorkspaceTab | null;
expanded: Record<string, false | Record<string, boolean>>;
expandedGroups: Record<string, boolean>;
groupsById?: Record<string, { id: string; name: string; color?: string }>;
onItemExpand(item: SidebarActionableItem, isExpanded: boolean): void;
onItemAction(item: SidebarActionableItem, action: Actions): void;
}
Expand All @@ -53,6 +57,8 @@ const ConnectionsNavigationTree: React.FunctionComponent<
connections,
activeWorkspace,
expanded,
expandedGroups,
groupsById = {},
onItemExpand,
onItemAction,
}) => {
Expand All @@ -62,12 +68,14 @@ const ConnectionsNavigationTree: React.FunctionComponent<
readOnly: preferencesReadOnly,
readWrite: preferencesReadWrite,
showDisabledConnections,
enableConnectionGroups,
} = usePreferences([
'enableShell',
'readOnly',
'readWrite',
'enableRenameCollectionModal',
'showDisabledConnections',
'enableConnectionGroups',
]);
const isRenameCollectionEnabled =
enableRenameCollectionModal && !preferencesReadWrite;
Expand All @@ -78,28 +86,43 @@ const ConnectionsNavigationTree: React.FunctionComponent<
const treeData = useMemo(() => {
return getVirtualTreeItems({
connections,
groupsById,
expandedItems: expanded,
expandedGroups,
enableConnectionGroups,
preferencesReadOnly,
preferencesReadWrite,
preferencesShellEnabled,
});
}, [
connections,
groupsById,
expanded,
expandedGroups,
enableConnectionGroups,
preferencesReadOnly,
preferencesReadWrite,
preferencesShellEnabled,
]);

const onDefaultAction: OnDefaultAction<SidebarActionableItem> = useCallback(
(item, evt) => {
if (showDisabledConnections) {
// A group is a container, not a connection, so the disabled-connection
// guard below must not short-circuit its default (row) action. Note that
// onDefaultAction never receives placeholder items (they are not
// actionable), so no placeholder check is needed here.
if (showDisabledConnections && item.type !== 'group') {
const connectionId = getConnectionId(item);
if (!getConnectable(connectionId)) {
return;
}
}

if (item.type === 'group') {
onItemExpand(item, !item.isExpanded);
return;
}

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

const activeItemId = useMemo(() => {
Expand Down Expand Up @@ -176,7 +199,11 @@ const ConnectionsNavigationTree: React.FunctionComponent<

const getItemActionsAndConfig = useCallback(
(item: SidebarTreeItem) => {
if (showDisabledConnections) {
if (
showDisabledConnections &&
item.type !== 'placeholder' &&
item.type !== 'group'
) {
const connectionId = getConnectionId(item);
if (!getConnectable(connectionId)) {
return {
Expand All @@ -189,6 +216,10 @@ const ConnectionsNavigationTree: React.FunctionComponent<
return {
actions: [],
};
case 'group':
return {
actions: groupItemActions(),
};
case 'connection': {
if (item.connectionStatus === 'connected') {
const actions = connectedConnectionItemActions({
Expand Down Expand Up @@ -249,6 +280,13 @@ const ConnectionsNavigationTree: React.FunctionComponent<
switch (item.type) {
case 'placeholder':
return [];
case 'group':
return itemActionsToContextMenuGroups(
'Group Tree Item',
item,
onItemAction,
groupContextMenuActions()
);
case 'connection':
return itemActionsToContextMenuGroups(
'Connection Tree Item',
Expand Down
5 changes: 4 additions & 1 deletion packages/compass-connections-navigation/src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ export type Actions =
| 'open-in-new-tab'
| 'duplicate-view'
| 'modify-view'
| 'rename-collection';
| 'rename-collection'
// group item related actions
| 'edit-group'
| 'delete-group';
31 changes: 31 additions & 0 deletions packages/compass-connections-navigation/src/item-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,37 @@ export const databaseContextMenuActions = ({
]);
};

export const groupItemActions = (): NavigationItemActions =>
stripNullActions([
{
action: 'edit-group',
label: 'Edit group',
icon: 'Edit',
},
{
action: 'delete-group',
label: 'Delete group',
icon: 'Trash',
variant: 'destructive',
},
]);

export const groupContextMenuActions = (): NavigationItemActions =>
stripNullActions([
{
action: 'edit-group',
label: 'Edit group',
icon: 'Edit',
},
{ separator: true },
{
action: 'delete-group',
label: 'Delete group',
icon: 'Trash',
variant: 'destructive',
},
]);

export const collectionContextMenuActions = ({
hasWriteActionsDisabled,
canEditCollection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const IconWithTooltip = ({
};

export const NavigationItemIcon = ({ item }: { item: SidebarTreeItem }) => {
if (item.type === 'group') {
return <Icon glyph="Folder" />;
}
if (item.type === 'database') {
if (item.inferredFromPrivileges) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ export function NavigationItem({
if (item.type === 'placeholder') {
return {};
}
if (item.type === 'group') {
return {
'data-group-name': item.groupName,
};
}
if (item.type === 'connection') {
return {
'data-is-active': isActive.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function StyledNavigationItem({
const isDarkMode = useDarkMode();
const { connectionColorToHex, connectionColorToHexActive } =
useConnectionColor();
const { colorCode } = item;
const colorCode = item.colorCode;
const inactiveColor = useMemo(
() => (isDarkMode ? palette.gray.light1 : palette.gray.dark1),
[isDarkMode]
Expand Down
Loading