diff --git a/packages/compass-app-stores/src/stores/instance-store.ts b/packages/compass-app-stores/src/stores/instance-store.ts index 27fa52958a9..281ec5786a8 100644 --- a/packages/compass-app-stores/src/stores/instance-store.ts +++ b/packages/compass-app-stores/src/stores/instance-store.ts @@ -2,6 +2,7 @@ import type { MongoDBInstanceProps } from 'mongodb-instance-model'; import { MongoDBInstance } from 'mongodb-instance-model'; import toNS from 'mongodb-ns'; import type { + ConnectionInfo, ConnectionsService, DataService, } from '@mongodb-js/compass-connections/provider'; @@ -14,6 +15,8 @@ import { openToast } from '@mongodb-js/compass-components'; import { MongoDBInstancesManager } from '../instances-manager'; import type { PreferencesAccess } from 'compass-preferences-model'; +type InstanceDetails = Awaited>; + function serversArray( serversMap: NonNullable< ReturnType @@ -305,55 +308,78 @@ export function createInstancesStore( instancesManager.removeMongoDBInstanceForConnection(connectionInfoId); }); - on(connections, 'connected', function (instanceConnectionId: string) { - const dataService = - connections.getDataServiceForConnection(instanceConnectionId); - const connectionString = dataService.getConnectionString(); - const firstHost = connectionString.hosts[0] || ''; - const [hostname, port] = firstHost.split(':'); - - const initialInstanceProps: Partial = { - _id: firstHost, - hostname: hostname, - port: port ? +port : undefined, - topologyDescription: getTopologyDescription( - dataService.getLastSeenTopology() - ), - preferences, - }; - const instance = instancesManager.createMongoDBInstanceForConnection( - instanceConnectionId, - initialInstanceProps as MongoDBInstanceProps - ); + on( + connections, + 'connected', + function ( + instanceConnectionId: string, + _connectionInfo: ConnectionInfo, + instanceInfo: InstanceDetails + ) { + const dataService = + connections.getDataServiceForConnection(instanceConnectionId); + const connectionString = dataService.getConnectionString(); + const firstHost = connectionString.hosts[0] || ''; + const [hostname, port] = (() => { + if (firstHost.startsWith('[')) { + return firstHost.slice(1).split(']'); // IPv6 + } + return firstHost.split(':'); + })(); + + const initialInstanceProps: Partial = { + // We pre-fetched instance info and so can right away construct it in a + // "ready" state + ...(instanceInfo as Partial), + status: 'ready', + statusError: null, + + // Required initial values that are not returned with instance info + _id: firstHost, + hostname: hostname, + port: port ? +port : undefined, + topologyDescription: getTopologyDescription( + dataService.getLastSeenTopology() + ), + + // Service injection for preferences (currently only controls namespace + // stats fetching) + preferences, + }; + const instance = instancesManager.createMongoDBInstanceForConnection( + instanceConnectionId, + initialInstanceProps as MongoDBInstanceProps + ); - addCleanup(() => { - instance.removeAllListeners(); - }); + addCleanup(() => { + instance.removeAllListeners(); + }); - void refreshInstance( - { - fetchDatabases: true, - fetchDbStats: true, - }, - { - connectionId: instanceConnectionId, - } - ); + void refreshInstance( + { + fetchDatabases: true, + fetchDbStats: true, + }, + { + connectionId: instanceConnectionId, + } + ); - on( - dataService, - 'topologyDescriptionChanged', - ({ - newDescription, - }: { - newDescription: ReturnType; - }) => { - instance.set({ - topologyDescription: getTopologyDescription(newDescription), - }); - } - ); - }); + on( + dataService, + 'topologyDescriptionChanged', + ({ + newDescription, + }: { + newDescription: ReturnType; + }) => { + instance.set({ + topologyDescription: getTopologyDescription(newDescription), + }); + } + ); + } + ); on( globalAppRegistry, diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts index 875573e719f..700cdf17e32 100644 --- a/packages/compass-connections/src/stores/connections-store-redux.ts +++ b/packages/compass-connections/src/stores/connections-store-redux.ts @@ -16,6 +16,7 @@ import type { ConnectionAttempt, ConnectionOptions, DataService, + InstanceDetails, } from 'mongodb-data-service'; import { createConnectionAttempt } from 'mongodb-data-service'; import { UUID } from 'bson'; @@ -43,7 +44,8 @@ import type { ImportConnectionOptions } from '@mongodb-js/connection-storage/pro export type ConnectionsEventMap = { connected: ( connectionId: ConnectionId, - connectionInfo: ConnectionInfo + connectionInfo: ConnectionInfo, + instanceInfo: InstanceDetails ) => void; disconnected: ( connectionId: ConnectionId, @@ -1658,6 +1660,14 @@ const connectWithOptions = ( 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 + // 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(); + let showedNonRetryableErrorToast = false; // Listen for non-retry-able errors on failed server heartbeats. // These can happen on compass web when: @@ -1762,13 +1772,17 @@ const connectWithOptions = ( track( 'New Connection', async () => { - const [ - { dataLake, genuineMongoDB, host, build, isAtlas, isLocalAtlas }, - [extraInfo, resolvedHostname], - ] = await Promise.all([ - dataService.instance(), - getExtraConnectionData(connectionInfo), - ]); + const { + dataLake, + genuineMongoDB, + host, + build, + isAtlas, + isLocalAtlas, + } = instanceInfo; + const [extraInfo, resolvedHostname] = await getExtraConnectionData( + connectionInfo + ); const connections = getState().connections; // Counting all connections, we need to filter out any connections currently being created @@ -1811,7 +1825,8 @@ const connectWithOptions = ( connectionsEventEmitter.emit( 'connected', connectionInfo.id, - connectionInfo + connectionInfo, + instanceInfo ); dispatch({ @@ -1819,36 +1834,24 @@ const connectWithOptions = ( connectionId: connectionInfo.id, }); - const { networkTraffic, showEndOfLifeConnectionModal } = - preferences.getPreferences(); - if ( getGenuineMongoDB(connectionInfo.connectionOptions.connectionString) .isGenuine === false ) { dispatch(showNonGenuineMongoDBWarningModal(connectionInfo.id)); - } else if (showEndOfLifeConnectionModal) { - void dataService - .instance() - .then(async (instance) => { - const { version } = instance.build; - const latestEndOfLifeServerVersion = - await getLatestEndOfLifeServerVersion(networkTraffic); - if (isEndOfLifeVersion(version, latestEndOfLifeServerVersion)) { - dispatch( - showEndOfLifeMongoDBWarningModal( - connectionInfo.id, - instance.build.version - ) - ); - } - }) - .catch((err) => { - debug( - 'failed to get instance details to determine if the server version is end-of-life', - err - ); - }); + } else if ( + await shouldShowEndOfLifeWarning( + instanceInfo.build.version, + preferences, + debug + ) + ) { + dispatch( + showEndOfLifeMongoDBWarningModal( + connectionInfo.id, + instanceInfo.build.version + ) + ); } } catch (err) { dispatch(connectionAttemptError(connectionInfo, err)); @@ -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 = ( connectionId: string, version: string diff --git a/packages/data-service/src/data-service.ts b/packages/data-service/src/data-service.ts index 0d6a10b1b21..98505e7440f 100644 --- a/packages/data-service/src/data-service.ts +++ b/packages/data-service/src/data-service.ts @@ -310,6 +310,8 @@ export interface DataService { /** * Get the current instance details. + * + * @deprecated avoid using `instance` directly and use `InstanceModel` instead */ instance(): Promise; @@ -340,6 +342,9 @@ export interface DataService { /** * List all collections for a database. + * + * @deprecated avoid using `listCollections` directly and use + * `CollectionModel` instead */ listCollections( databaseName: string, @@ -448,6 +453,9 @@ export interface DataService { /** * List all databases on the currently connected instance. + * + * @deprecated avoid using `listDatabases` directly and use `DatabaseModel` + * instead */ listDatabases(options?: { nameOnly?: true;