Skip to content
Merged
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
32 changes: 29 additions & 3 deletions packages/compass-app-stores/src/stores/instance-store.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MongoDBInstanceProps } from 'mongodb-instance-model';
import { MongoDBInstance } from 'mongodb-instance-model'; import { MongoDBInstance } from 'mongodb-instance-model';
import toNS from 'mongodb-ns'; import toNS from 'mongodb-ns';
import type { import type {
ConnectionInfo,
ConnectionsService, ConnectionsService,
DataService, DataService,
} from '@mongodb-js/compass-connections/provider'; } from '@mongodb-js/compass-connections/provider';
Expand All @@ -14,6 +15,8 @@ import { openToast } from '@mongodb-js/compass-components';
import { MongoDBInstancesManager } from '../instances-manager'; import { MongoDBInstancesManager } from '../instances-manager';
import type { PreferencesAccess } from 'compass-preferences-model'; import type { PreferencesAccess } from 'compass-preferences-model';


type InstanceDetails = Awaited<ReturnType<DataService['instance']>>;

function serversArray( function serversArray(
serversMap: NonNullable< serversMap: NonNullable<
ReturnType<DataService['getLastSeenTopology']> ReturnType<DataService['getLastSeenTopology']>
Expand Down Expand Up @@ -305,20 +308,42 @@ export function createInstancesStore(
instancesManager.removeMongoDBInstanceForConnection(connectionInfoId); instancesManager.removeMongoDBInstanceForConnection(connectionInfoId);
}); });


on(connections, 'connected', function (instanceConnectionId: string) { on(
connections,
'connected',
function (
instanceConnectionId: string,
Copy link
Contributor

Choose a reason for hiding this comment

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

We're now tacking on more positional arguments. Wouldn't one object where you can just pick what you want from it start to make more sense? Or do we have too many things using this and that would involve lots of changes? Or is there some other reason I can't immediately spot why we can't change it?

Copy link
Collaborator Author

@gribnoysup gribnoysup Jul 14, 2025

Choose a reason for hiding this comment

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

I'll take a closer look at the usage, adding a positional was just the easiest for me, but generally speaking yes, we can probably pack it all into one object (also instanceConnectionId is just connectionInfo.id, so I'll just drop it 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.

I went back and forth on this and decided not to change this, I started moving them to an object of properties, but then disconnect event is different and wanted to keep them consistent, but then connectionId is like the only used argument in disconnect and having it inside an object was weird, so I was trying different combinations, like maybe keeping connectionId as an argument, but moving everything else into an object in a second argument, and this also didn't look right, then I thought I'm overthinking it and it was cleaner to just keep it as is. Hope that's okay

_connectionInfo: ConnectionInfo,
instanceInfo: InstanceDetails
) {
const dataService = const dataService =
connections.getDataServiceForConnection(instanceConnectionId); connections.getDataServiceForConnection(instanceConnectionId);
const connectionString = dataService.getConnectionString(); const connectionString = dataService.getConnectionString();
const firstHost = connectionString.hosts[0] || ''; const firstHost = connectionString.hosts[0] || '';
const [hostname, port] = firstHost.split(':'); const [hostname, port] = (() => {
if (firstHost.startsWith('[')) {
return firstHost.slice(1).split(']'); // IPv6
}
return firstHost.split(':');
})();


const initialInstanceProps: Partial<MongoDBInstanceProps> = { const initialInstanceProps: Partial<MongoDBInstanceProps> = {
// We pre-fetched instance info and so can right away construct it in a
// "ready" state
...(instanceInfo as Partial<MongoDBInstanceProps>),
status: 'ready',
statusError: null,

// Required initial values that are not returned with instance info
_id: firstHost, _id: firstHost,
hostname: hostname, hostname: hostname,
port: port ? +port : undefined, port: port ? +port : undefined,
topologyDescription: getTopologyDescription( topologyDescription: getTopologyDescription(
dataService.getLastSeenTopology() dataService.getLastSeenTopology()
), ),

// Service injection for preferences (currently only controls namespace
// stats fetching)
preferences, preferences,
}; };
const instance = instancesManager.createMongoDBInstanceForConnection( const instance = instancesManager.createMongoDBInstanceForConnection(
Expand Down Expand Up @@ -353,7 +378,8 @@ export function createInstancesStore(
}); });
} }
); );
}); }
);


on( on(
globalAppRegistry, globalAppRegistry,
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
ConnectionAttempt, ConnectionAttempt,
ConnectionOptions, ConnectionOptions,
DataService, DataService,
InstanceDetails,
} from 'mongodb-data-service'; } from 'mongodb-data-service';
import { createConnectionAttempt } from 'mongodb-data-service'; import { createConnectionAttempt } from 'mongodb-data-service';
import { UUID } from 'bson'; import { UUID } from 'bson';
Expand Down Expand Up @@ -43,7 +44,8 @@ import type { ImportConnectionOptions } from '@mongodb-js/connection-storage/pro
export type ConnectionsEventMap = { export type ConnectionsEventMap = {
connected: ( connected: (
connectionId: ConnectionId, connectionId: ConnectionId,
connectionInfo: ConnectionInfo connectionInfo: ConnectionInfo,
instanceInfo: InstanceDetails
) => void; ) => void;
disconnected: ( disconnected: (
connectionId: ConnectionId, connectionId: ConnectionId,
Expand Down Expand Up @@ -1658,6 +1660,14 @@ const connectWithOptions = (
return; return;
} }


// We're trying to optimise the initial Compass loading times here: to
// make sure that the driver connection pool doesn't immediately get
// overwhelmed with requests, we fetch instance info only once and then
Comment on lines +1663 to +1665
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea, in fact it doesn't really matter what we set our pool size to, browsers do not permit more than 6 parallel connections to be creating at the same time.

It obviously matters for our total count in the long run, but at least at boot that's our bottleneck.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We still have multiplexing in the plans, right? Just wondering

// pass it down to telemetry and instance model. This is a relatively
// expensive dataService operation so we're trying to keep the usage
// very limited
const instanceInfo = await dataService.instance();
Copy link
Collaborator Author

@gribnoysup gribnoysup Jul 3, 2025

Choose a reason for hiding this comment

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

This is somewhat a breaking change in Compass behavior as described in COMPASS-7675 but I think it's probably okay to do so as we were already discussing doing this. Basically the main change in the behavior here is that some connection related errors that would show up in this weird state where you connected, but we didn't even manage to get to the listing database part of the logic before now will start showing up as part of the connection flow, this doesn't sound too bad to me, but I also asked Betsy to confirm in the ticket


let showedNonRetryableErrorToast = false; let showedNonRetryableErrorToast = false;
// Listen for non-retry-able errors on failed server heartbeats. // Listen for non-retry-able errors on failed server heartbeats.
// These can happen on compass web when: // These can happen on compass web when:
Expand Down Expand Up @@ -1762,13 +1772,17 @@ const connectWithOptions = (
track( track(
'New Connection', 'New Connection',
async () => { async () => {
const [ const {
{ dataLake, genuineMongoDB, host, build, isAtlas, isLocalAtlas }, dataLake,
[extraInfo, resolvedHostname], genuineMongoDB,
] = await Promise.all([ host,
dataService.instance(), build,
getExtraConnectionData(connectionInfo), isAtlas,
]); isLocalAtlas,
} = instanceInfo;
const [extraInfo, resolvedHostname] = await getExtraConnectionData(
connectionInfo
);


const connections = getState().connections; const connections = getState().connections;
// Counting all connections, we need to filter out any connections currently being created // Counting all connections, we need to filter out any connections currently being created
Expand Down Expand Up @@ -1811,45 +1825,34 @@ const connectWithOptions = (
connectionsEventEmitter.emit( connectionsEventEmitter.emit(
'connected', 'connected',
connectionInfo.id, connectionInfo.id,
connectionInfo connectionInfo,
instanceInfo
); );


dispatch({ dispatch({
type: ActionTypes.ConnectionAttemptSuccess, type: ActionTypes.ConnectionAttemptSuccess,
connectionId: connectionInfo.id, connectionId: connectionInfo.id,
}); });


const { networkTraffic, showEndOfLifeConnectionModal } =
preferences.getPreferences();

if ( if (
getGenuineMongoDB(connectionInfo.connectionOptions.connectionString) getGenuineMongoDB(connectionInfo.connectionOptions.connectionString)
.isGenuine === false .isGenuine === false
) { ) {
dispatch(showNonGenuineMongoDBWarningModal(connectionInfo.id)); dispatch(showNonGenuineMongoDBWarningModal(connectionInfo.id));
} else if (showEndOfLifeConnectionModal) { } else if (
void dataService await shouldShowEndOfLifeWarning(
.instance() instanceInfo.build.version,
.then(async (instance) => { preferences,
const { version } = instance.build; debug
const latestEndOfLifeServerVersion = )
await getLatestEndOfLifeServerVersion(networkTraffic); ) {
if (isEndOfLifeVersion(version, latestEndOfLifeServerVersion)) {
dispatch( dispatch(
showEndOfLifeMongoDBWarningModal( showEndOfLifeMongoDBWarningModal(
connectionInfo.id, connectionInfo.id,
instance.build.version instanceInfo.build.version
) )
); );
} }
})
.catch((err) => {
debug(
'failed to get instance details to determine if the server version is end-of-life',
err
);
});
}
} catch (err) { } catch (err) {
dispatch(connectionAttemptError(connectionInfo, err)); dispatch(connectionAttemptError(connectionInfo, err));
} finally { } finally {
Expand Down Expand Up @@ -2173,6 +2176,30 @@ export const showNonGenuineMongoDBWarningModal = (
}; };
}; };


async function shouldShowEndOfLifeWarning(
serverVersion: string,
preferences: PreferencesAccess,
debug: Logger['debug']
) {
try {
const { showEndOfLifeConnectionModal, networkTraffic } =
preferences.getPreferences();
if (!showEndOfLifeConnectionModal) {
return;
}
const latestEndOfLifeServerVersion = await getLatestEndOfLifeServerVersion(
networkTraffic
);
return isEndOfLifeVersion(serverVersion, latestEndOfLifeServerVersion);
} catch (err) {
debug(
'failed to get instance details to determine if the server version is end-of-life',
err
);
return false;
}
}

export const showEndOfLifeMongoDBWarningModal = ( export const showEndOfLifeMongoDBWarningModal = (
connectionId: string, connectionId: string,
version: string version: string
Expand Down
8 changes: 8 additions & 0 deletions packages/data-service/src/data-service.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ export interface DataService {


/** /**
* Get the current instance details. * Get the current instance details.
*
* @deprecated avoid using `instance` directly and use `InstanceModel` instead
Copy link
Collaborator Author

@gribnoysup gribnoysup Jul 3, 2025

Choose a reason for hiding this comment

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

Literally everywhere except for those initial / inside models fetches we should be using models instead, so I'm adding this marking to make IDEs highlight the method usage

*/ */
instance(): Promise<InstanceDetails>; instance(): Promise<InstanceDetails>;


Expand Down Expand Up @@ -340,6 +342,9 @@ export interface DataService {


/** /**
* List all collections for a database. * List all collections for a database.
*
* @deprecated avoid using `listCollections` directly and use
* `CollectionModel` instead
*/ */
listCollections( listCollections(
databaseName: string, databaseName: string,
Expand Down Expand Up @@ -448,6 +453,9 @@ export interface DataService {


/** /**
* List all databases on the currently connected instance. * List all databases on the currently connected instance.
*
* @deprecated avoid using `listDatabases` directly and use `DatabaseModel`
* instead
*/ */
listDatabases(options?: { listDatabases(options?: {
nameOnly?: true; nameOnly?: true;
Expand Down
Loading