= ({
{connections.length > 0 && (
<>
-
+
+
+
+ {excludeInactive ? (
+
+ ) : (
+
+ )}
+
+ }
+ >
+ {excludeInactive
+ ? 'Showing active connections'
+ : 'Showing all connections'}
+
+
{
+ await renderAndWaitForNavigationTree();
+
+ const favoriteConnectionId = savedFavoriteConnection.id;
+ const recentConnectionId = savedRecentConnection.id;
+
+ const activeConnectionsToggleButton = screen.getByLabelText(
+ 'Showing all connections'
+ );
+
+ expect(screen.queryByTestId(favoriteConnectionId)).to.be.visible;
+ expect(screen.queryByTestId(recentConnectionId)).to.be.visible;
+
+ userEvent.click(activeConnectionsToggleButton);
+ expect(activeConnectionsToggleButton.ariaLabel).equals(
+ 'Showing active connections'
+ );
+
+ expect(screen.queryByTestId(favoriteConnectionId)).to.be.null;
+ expect(screen.queryByTestId(recentConnectionId)).to.be.null;
+
+ await connectAndNotifyInstanceManager(savedFavoriteConnection);
+ expect(screen.queryByTestId(favoriteConnectionId)).to.be.visible;
+ expect(screen.queryByTestId(recentConnectionId)).to.be.null;
+
+ await connectAndNotifyInstanceManager(savedRecentConnection);
+ expect(screen.queryByTestId(favoriteConnectionId)).to.be.visible;
+ expect(screen.queryByTestId(recentConnectionId)).to.be.visible;
+ });
+
context('and performing actions', function () {
beforeEach(async function () {
await renderAndWaitForNavigationTree({
diff --git a/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx b/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx
index 3abebc22e78..d21a551b368 100644
--- a/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx
+++ b/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx
@@ -103,6 +103,10 @@ export function MultipleConnectionSidebar({
useState(null);
const [connectionInfoModalConnectionId, setConnectionInfoModalConnectionId] =
useState();
+ const [excludeInactive, setExcludeInactiveConnections] = useState(false);
+ const toggleExcludeInactiveConnections = useCallback(() => {
+ setExcludeInactiveConnections((previous) => !previous);
+ }, []);
const formPreferences = useConnectionFormPreferences();
const maybeProtectConnectionString = useMaybeProtectConnectionString();
@@ -204,7 +208,9 @@ export function MultipleConnectionSidebar({
connectionsWithStatus={connectionsWithStatus}
activeWorkspace={activeWorkspace}
filterRegex={activeConnectionsFilterRegex}
+ excludeInactive={excludeInactive}
onFilterChange={onActiveConnectionFilterChange}
+ onToggleExcludeInactive={toggleExcludeInactiveConnections}
onConnect={(connectionInfo) => {
void connect(connectionInfo);
}}
diff --git a/packages/compass-sidebar/src/components/navigation-items-filter.tsx b/packages/compass-sidebar/src/components/navigation-items-filter.tsx
index 7601816238e..98c7abf4e57 100644
--- a/packages/compass-sidebar/src/components/navigation-items-filter.tsx
+++ b/packages/compass-sidebar/src/components/navigation-items-filter.tsx
@@ -6,13 +6,13 @@ export default function NavigationItemsFilter({
ariaLabel = 'Search',
title = 'Search',
onFilterChange,
- searchInputClassName,
+ className,
}: {
placeholder?: string;
ariaLabel?: string;
title?: string;
- searchInputClassName?: string;
onFilterChange(regex: RegExp | null): void;
+ className?: string;
}): React.ReactElement {
const onChange = useCallback(
(event) => {
@@ -37,7 +37,7 @@ export default function NavigationItemsFilter({
}, []);
return (
-
);
diff --git a/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts b/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts
index dae61d4cf34..5d93c0b65e1 100644
--- a/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts
+++ b/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts
@@ -140,6 +140,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -155,6 +156,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -167,6 +169,38 @@ describe('useFilteredConnections', function () {
});
});
+ context('excluding inactive connections', function () {
+ it('should match only connected collections items', function () {
+ const { result, rerender } = renderHookWithContext(
+ useFilteredConnections,
+ {
+ initialProps: {
+ connections: mockSidebarConnections,
+ filterRegex: null,
+ fetchAllCollections: fetchAllCollectionsStub,
+ onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: true,
+ },
+ }
+ );
+
+ expect(result.current.filtered).to.be.deep.equal([
+ sidebarConnections[0],
+ sidebarConnections[1],
+ ]);
+
+ rerender({
+ connections: mockSidebarConnections,
+ filterRegex: null,
+ fetchAllCollections: fetchAllCollectionsStub,
+ onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
+ });
+
+ expect(result.current.filtered).to.be.undefined;
+ });
+ });
+
context('and a connection is toggled', function () {
it('should return the appropriate connection expanded state for the toggled connection', async function () {
const { result } = renderHookWithContext(useFilteredConnections, {
@@ -175,6 +209,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -206,6 +241,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -258,6 +294,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -294,6 +331,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -340,6 +378,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -373,6 +412,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
}
);
@@ -407,6 +447,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
});
// should remove the expanded state of connection 2
await waitFor(() => {
@@ -423,6 +464,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
});
await waitFor(() => {
expect(result.current.expanded).to.deep.equal({
@@ -445,6 +487,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('_connection', 'i'), // match everything basically
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
}
);
@@ -460,6 +503,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('disconnected_connection', 'i'), // match disconnected one
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
});
await waitFor(() => {
expect(result.current.filtered).to.be.deep.equal([
@@ -475,6 +519,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('db_ready_1_1', 'i'), // match first database basically
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -515,6 +560,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('Matching', 'i'), // this matches connection as well as database
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -532,6 +578,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('coll_ready_2_1', 'i'), // match second db's collection
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -560,6 +607,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('ready_2_1', 'i'), // this matches 1 database and 1 collection
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -578,6 +626,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('coll_ready_1_1', 'i'),
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -592,6 +641,40 @@ describe('useFilteredConnections', function () {
});
});
+ context('excluding inactive connections', function () {
+ it('should match only connected collections items', function () {
+ const { result, rerender } = renderHookWithContext(
+ useFilteredConnections,
+ {
+ initialProps: {
+ connections: mockSidebarConnections,
+ filterRegex: new RegExp('connection_1'),
+ fetchAllCollections: fetchAllCollectionsStub,
+ onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: true,
+ },
+ }
+ );
+
+ expect(result.current.filtered).to.be.deep.equal([
+ sidebarConnections[0],
+ ]);
+
+ rerender({
+ connections: mockSidebarConnections,
+ filterRegex: new RegExp('connection_1'),
+ fetchAllCollections: fetchAllCollectionsStub,
+ onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
+ });
+
+ expect(result.current.filtered).to.be.deep.equal([
+ sidebarConnections[0],
+ sidebarConnections[2],
+ ]);
+ });
+ });
+
context('and items are already collapsed', function () {
it('should expand the items temporarily', async function () {
const { result, rerender } = renderHookWithContext(
@@ -599,9 +682,10 @@ describe('useFilteredConnections', function () {
{
initialProps: {
connections: mockSidebarConnections,
- filterRegex: null,
+ filterRegex: null as RegExp | null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
}
);
@@ -620,6 +704,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('coll_ready_1_1', 'i'),
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
});
await waitFor(() => {
expect(result.current.expanded).to.deep.equal({
@@ -640,9 +725,10 @@ describe('useFilteredConnections', function () {
{
initialProps: {
connections: mockSidebarConnections,
- filterRegex: new RegExp('coll_ready_1_1', 'i'),
+ filterRegex: new RegExp('coll_ready_1_1', 'i') as RegExp | null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
}
);
@@ -661,6 +747,7 @@ describe('useFilteredConnections', function () {
filterRegex: null,
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
});
await waitFor(() => {
expect(result.current.expanded).to.deep.equal({
@@ -682,6 +769,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('coll_ready_1_1', 'i'),
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -714,6 +802,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('coll_ready_1_1', 'i'),
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
@@ -752,6 +841,7 @@ describe('useFilteredConnections', function () {
filterRegex: new RegExp('coll_ready_1_1', 'i'),
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
+ excludeInactive: false,
},
});
diff --git a/packages/compass-sidebar/src/components/use-filtered-connections.ts b/packages/compass-sidebar/src/components/use-filtered-connections.ts
index 2d882324dee..a90c6589c9f 100644
--- a/packages/compass-sidebar/src/components/use-filtered-connections.ts
+++ b/packages/compass-sidebar/src/components/use-filtered-connections.ts
@@ -42,11 +42,19 @@ type FilteredConnection = (
const filterConnections = (
connections: SidebarConnection[],
- regex: RegExp
+ regex: RegExp | null,
+ excludeInactive: boolean
): FilteredConnection[] => {
const results: FilteredConnection[] = [];
for (const connection of connections) {
- const isMatch = regex.test(connection.name);
+ // Conditionally skip connections that aren't considered active
+ const inactive =
+ connection.connectionStatus !== 'connected' &&
+ connection.connectionStatus !== 'connecting';
+ if (excludeInactive && inactive) {
+ continue;
+ }
+ const isMatch = !regex || regex.test(connection.name);
let childMatches: FilteredDatabase[] = [];
if (connection.connectionStatus === 'connected') {
childMatches = filterDatabases(connection.databases, regex);
@@ -72,11 +80,11 @@ const filterConnections = (
const filterDatabases = (
databases: SidebarDatabase[],
- regex: RegExp
+ regex: RegExp | null
): FilteredDatabase[] => {
const results: FilteredDatabase[] = [];
for (const db of databases) {
- const isMatch = regex.test(db.name);
+ const isMatch = !regex || regex.test(db.name);
const childMatches = filterCollections(db.collections, regex);
if (isMatch || childMatches.length) {
@@ -89,7 +97,7 @@ const filterDatabases = (
? childMatches
: db.collections.map((collection) => ({
...collection,
- isMatch: regex.test(collection.name),
+ isMatch: !regex || regex.test(collection.name),
}));
results.push({
...db,
@@ -103,10 +111,10 @@ const filterDatabases = (
const filterCollections = (
collections: SidebarCollection[],
- regex: RegExp
+ regex: RegExp | null
): FilteredCollection[] => {
return collections
- .filter(({ name }) => regex.test(name))
+ .filter(({ name }) => !regex || regex.test(name))
.map((collection) => ({ ...collection, isMatch: true }));
};
@@ -205,7 +213,8 @@ const FILTER_CONNECTIONS =
interface FilterConnectionsAction {
type: typeof FILTER_CONNECTIONS;
connections: SidebarConnection[];
- filterRegex: RegExp;
+ filterRegex: RegExp | null;
+ excludeInactive: boolean;
}
const CLEAR_FILTER = 'sidebar/active-connections/CLEAR_FILTER' as const;
@@ -265,7 +274,8 @@ const connectionsReducer = (
case FILTER_CONNECTIONS: {
const filtered = filterConnections(
action.connections,
- action.filterRegex
+ action.filterRegex,
+ action.excludeInactive
);
const persistingExpanded = revertTemporaryExpanded(state.expanded);
return {
@@ -381,11 +391,13 @@ function filteredConnectionsToSidebarConnection(
export const useFilteredConnections = ({
connections,
filterRegex,
+ excludeInactive,
fetchAllCollections,
onDatabaseExpand,
}: {
connections: SidebarConnection[];
filterRegex: RegExp | null;
+ excludeInactive: boolean;
fetchAllCollections: () => void;
onDatabaseExpand: (connectionId: string, databaseId: string) => void;
}): UseFilteredConnectionsHookResult => {
@@ -410,11 +422,12 @@ export const useFilteredConnections = ({
// filter updates
// connections change often, but the effect only uses connections if the filter is active
// so we use this conditional dependency to avoid too many calls
- const connectionsButOnlyIfFilterIsActive = filterRegex && connections;
+ const connectionsWhenFiltering =
+ (filterRegex || excludeInactive) && connections;
useEffect(() => {
- if (!filterRegex) {
+ if (!filterRegex && !excludeInactive) {
dispatch({ type: CLEAR_FILTER });
- } else if (connectionsButOnlyIfFilterIsActive) {
+ } else if (connectionsWhenFiltering) {
// the above check is extra just to please TS
// When filtering, emit an event so that we can fetch all collections. This
@@ -424,11 +437,17 @@ export const useFilteredConnections = ({
dispatch({
type: FILTER_CONNECTIONS,
- connections: connectionsButOnlyIfFilterIsActive,
+ connections: connectionsWhenFiltering,
filterRegex,
+ excludeInactive,
});
}
- }, [filterRegex, connectionsButOnlyIfFilterIsActive, fetchAllCollections]);
+ }, [
+ filterRegex,
+ excludeInactive,
+ connectionsWhenFiltering,
+ fetchAllCollections,
+ ]);
const onConnectionToggle = useCallback(
(connectionId: string, expand: boolean) =>