diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx
index a9cad9faa4a..6f3bf3bb79d 100644
--- a/configs/testing-library-compass/src/index.tsx
+++ b/configs/testing-library-compass/src/index.tsx
@@ -181,7 +181,8 @@ export class MockDataService
},
build: {
isEnterprise: false,
- version: '0.0.0',
+ // Picking a large version to avoid the end-of-life confirmation modal
+ version: '100.0.0',
},
host: {},
genuineMongoDB: {
diff --git a/package-lock.json b/package-lock.json
index f0b1aedda62..af0d03e7398 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43968,7 +43968,8 @@
"react": "^17.0.2",
"react-redux": "^8.1.3",
"redux": "^4.2.1",
- "redux-thunk": "^2.4.2"
+ "redux-thunk": "^2.4.2",
+ "semver": "^7.6.2"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.3.8",
@@ -43981,6 +43982,7 @@
"@types/mocha": "^9.0.0",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.10",
+ "@types/semver": "^7.3.9",
"@types/sinon-chai": "^3.2.5",
"chai": "^4.3.4",
"depcheck": "^1.4.1",
@@ -56395,6 +56397,7 @@
"@types/mocha": "^9.0.0",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.10",
+ "@types/semver": "^7.3.9",
"@types/sinon-chai": "^3.2.5",
"bson": "^6.10.3",
"chai": "^4.3.4",
@@ -56414,6 +56417,7 @@
"react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
+ "semver": "^7.6.2",
"sinon": "^9.2.3",
"xvfb-maybe": "^0.2.1"
},
diff --git a/packages/compass-app-stores/src/provider.spec.tsx b/packages/compass-app-stores/src/provider.spec.tsx
index e979c2ddcd3..646ffac645e 100644
--- a/packages/compass-app-stores/src/provider.spec.tsx
+++ b/packages/compass-app-stores/src/provider.spec.tsx
@@ -76,7 +76,10 @@ describe('NamespaceProvider', function () {
const instance = instanceManager.getMongoDBInstanceForConnection();
sandbox.stub(instance, 'fetchDatabases').callsFake(() => {
instance.databases.add({ _id: 'foo' });
- return Promise.resolve();
+ // Wait a tick before resolving the promise to simulate async behavior
+ return new Promise((resolve) => {
+ setTimeout(resolve);
+ });
});
await renderWithActiveConnection(
diff --git a/packages/compass-app-stores/src/stores/instance-store.spec.ts b/packages/compass-app-stores/src/stores/instance-store.spec.ts
index b33c7355007..768a609945e 100644
--- a/packages/compass-app-stores/src/stores/instance-store.spec.ts
+++ b/packages/compass-app-stores/src/stores/instance-store.spec.ts
@@ -125,7 +125,7 @@ describe('InstanceStore [Store]', function () {
const instance = instancesManager.getMongoDBInstanceForConnection(
connectedConnectionInfoId
);
- expect(instance).to.have.nested.property('build.version', '0.0.0');
+ expect(instance).to.have.nested.property('build.version', '100.0.0');
globalAppRegistry.emit('refresh-data');
await waitForInstanceRefresh(instance);
});
diff --git a/packages/compass-connections/package.json b/packages/compass-connections/package.json
index ef1388c8466..11dc3de1db0 100644
--- a/packages/compass-connections/package.json
+++ b/packages/compass-connections/package.json
@@ -69,7 +69,8 @@
"react": "^17.0.2",
"react-redux": "^8.1.3",
"redux": "^4.2.1",
- "redux-thunk": "^2.4.2"
+ "redux-thunk": "^2.4.2",
+ "semver": "^7.6.2"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.3.8",
@@ -82,6 +83,7 @@
"@types/mocha": "^9.0.0",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.10",
+ "@types/semver": "^7.3.9",
"@types/sinon-chai": "^3.2.5",
"chai": "^4.3.4",
"depcheck": "^1.4.1",
diff --git a/packages/compass-connections/src/components/end-of-life-connection-modal.tsx b/packages/compass-connections/src/components/end-of-life-connection-modal.tsx
new file mode 100644
index 00000000000..616f335ba10
--- /dev/null
+++ b/packages/compass-connections/src/components/end-of-life-connection-modal.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import {
+ css,
+ Banner,
+ Link,
+ spacing,
+ Body,
+ BannerVariant,
+ showConfirmation,
+} from '@mongodb-js/compass-components';
+import {
+ getConnectionTitle,
+ type ConnectionInfo,
+} from '@mongodb-js/connection-info';
+
+const modalBodyStyles = css({
+ marginTop: spacing[400],
+ marginBottom: spacing[200],
+});
+
+export function showEndOfLifeMongoDBWarningModal(
+ connectionInfo?: ConnectionInfo,
+ version?: string
+) {
+ return showConfirmation({
+ title: 'End-of-life MongoDB Detected',
+ hideCancelButton: true,
+ description: (
+ <>
+
+ {connectionInfo
+ ? `Server or service "${getConnectionTitle(connectionInfo)}"`
+ : 'This server or service'}{' '}
+ appears to be running a version of MongoDB that is no longer
+ supported.
+
+
+ Server version{version ? ` (${version})` : ''} is considered
+ end-of-life, consider upgrading to get the latest features and
+ performance improvements.{' '}
+
+
+ Learn more from the MongoDB Lifecycle Schedules.
+
+ >
+ ),
+ });
+}
diff --git a/packages/compass-connections/src/components/non-genuine-connection-modal.tsx b/packages/compass-connections/src/components/non-genuine-connection-modal.tsx
index 20eb41af456..eada1e085c9 100644
--- a/packages/compass-connections/src/components/non-genuine-connection-modal.tsx
+++ b/packages/compass-connections/src/components/non-genuine-connection-modal.tsx
@@ -23,6 +23,7 @@ export function showNonGenuineMongoDBWarningModal(
) {
return showConfirmation({
title: 'Non-Genuine MongoDB Detected',
+ hideCancelButton: true,
description: (
<>
diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts
index e6b0f8a7c4d..339f823cacf 100644
--- a/packages/compass-connections/src/stores/connections-store-redux.ts
+++ b/packages/compass-connections/src/stores/connections-store-redux.ts
@@ -30,9 +30,14 @@ import { adjustConnectionOptionsBeforeConnect } from '@mongodb-js/connection-for
import mongodbBuildInfo, { getGenuineMongoDB } from 'mongodb-build-info';
import EventEmitter from 'events';
import { showNonGenuineMongoDBWarningModal as _showNonGenuineMongoDBWarningModal } from '../components/non-genuine-connection-modal';
+import { showEndOfLifeMongoDBWarningModal as _showEndOfLifeMongoDBWarningModal } from '../components/end-of-life-connection-modal';
import ConnectionString from 'mongodb-connection-string-url';
import type { ExtraConnectionData as ExtraConnectionDataForTelemetry } from '@mongodb-js/compass-telemetry';
import { connectable } from '../utils/connection-supports';
+import {
+ getLatestEndOfLifeServerVersion,
+ isEndOfLifeVersion,
+} from '../utils/end-of-life-server';
export type ConnectionsEventMap = {
connected: (
@@ -1818,6 +1823,28 @@ const connectWithOptions = (
.isGenuine === false
) {
dispatch(showNonGenuineMongoDBWarningModal(connectionInfo.id));
+ } else if (preferences.getPreferences().networkTraffic) {
+ void dataService
+ .instance()
+ .then(async (instance) => {
+ const { version } = instance.build;
+ const latestEndOfLifeServerVersion =
+ await getLatestEndOfLifeServerVersion();
+ 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
+ );
+ });
}
} catch (err) {
dispatch(connectionAttemptError(connectionInfo, err));
@@ -2142,6 +2169,17 @@ export const showNonGenuineMongoDBWarningModal = (
};
};
+export const showEndOfLifeMongoDBWarningModal = (
+ connectionId: string,
+ version: string
+): ConnectionsThunkAction => {
+ return (_dispatch, getState, { track }) => {
+ const connectionInfo = getCurrentConnectionInfo(getState(), connectionId);
+ track('Screen', { name: 'end_of_life_mongodb_modal' }, connectionInfo);
+ void _showEndOfLifeMongoDBWarningModal(connectionInfo, version);
+ };
+};
+
type ImportConnectionsFn = Required['importConnections'];
export const importConnections = (
diff --git a/packages/compass-connections/src/utils/end-of-life-server.spec.ts b/packages/compass-connections/src/utils/end-of-life-server.spec.ts
new file mode 100644
index 00000000000..ad0e0271008
--- /dev/null
+++ b/packages/compass-connections/src/utils/end-of-life-server.spec.ts
@@ -0,0 +1,31 @@
+import { expect } from 'chai';
+import { isEndOfLifeVersion } from './end-of-life-server';
+
+describe('isEndOfLifeVersion', function () {
+ const LATEST_END_OF_LIFE_VERSION = '4.4.x';
+
+ function expectVersions(versions: string[], expected: boolean) {
+ for (const version of versions) {
+ expect(isEndOfLifeVersion(version, LATEST_END_OF_LIFE_VERSION)).to.equal(
+ expected,
+ `Expected ${version} to be ${
+ expected ? 'end of life' : 'not end of life'
+ }`
+ );
+ }
+ }
+
+ it('returns true for v4.4 and below', () => {
+ expectVersions(
+ ['4.4.0', '4.3.0', '4.0', '4.0-beta.0', '1.0.0', '0.0.1', '3.999.0'],
+ true
+ );
+ });
+
+ it('returns true for v4.5 and above', () => {
+ expectVersions(
+ ['4.5.0', '5.0.0', '5.0.25', '6.0.0', '7.0.0', '8.0.0'],
+ false
+ );
+ });
+});
diff --git a/packages/compass-connections/src/utils/end-of-life-server.ts b/packages/compass-connections/src/utils/end-of-life-server.ts
new file mode 100644
index 00000000000..fd8f06fb245
--- /dev/null
+++ b/packages/compass-connections/src/utils/end-of-life-server.ts
@@ -0,0 +1,83 @@
+import semverSatisfies from 'semver/functions/satisfies';
+import semverCoerce from 'semver/functions/coerce';
+
+import { createLogger } from '@mongodb-js/compass-logging';
+
+const { mongoLogId, log, debug } = createLogger('END-OF-LIFE-SERVER');
+
+const FALLBACK_END_OF_LIFE_SERVER_VERSION = '4.4';
+const {
+ HADRON_AUTO_UPDATE_ENDPOINT = process.env
+ .HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE,
+} = process.env;
+
+let latestEndOfLifeServerVersion: Promise | null = null;
+
+export async function getLatestEndOfLifeServerVersion(): Promise {
+ if (!HADRON_AUTO_UPDATE_ENDPOINT) {
+ log.debug(
+ mongoLogId(1_001_000_352),
+ 'getLatestEndOfLifeServerVersion',
+ 'HADRON_AUTO_UPDATE_ENDPOINT is not set'
+ );
+ return FALLBACK_END_OF_LIFE_SERVER_VERSION;
+ }
+
+ if (!latestEndOfLifeServerVersion) {
+ // Setting module scoped variable to avoid repeated fetches.
+ log.debug(
+ mongoLogId(1_001_000_353),
+ 'getLatestEndOfLifeServerVersion',
+ 'Fetching EOL server version'
+ );
+ latestEndOfLifeServerVersion = fetch(
+ `${HADRON_AUTO_UPDATE_ENDPOINT}/api/v2/eol-server`
+ )
+ .then(async (response) => {
+ if (response.ok) {
+ const result = await response.text();
+ log.debug(
+ mongoLogId(1_001_000_354),
+ 'getLatestEndOfLifeServerVersion',
+ 'Got EOL server version response',
+ { result }
+ );
+ return result;
+ } else {
+ // Reset the cached value to null so that we can try again next time.
+ latestEndOfLifeServerVersion = null;
+ throw new Error(
+ `Expected an OK response, got ${response.status} '${response.statusText}'`
+ );
+ }
+ })
+ .catch((error) => {
+ log.error(
+ mongoLogId(1_001_000_355),
+ 'getLatestEndOfLifeServerVersion',
+ 'Failed to fetch EOL server version',
+ { error }
+ );
+ // We don't want any downstream code to fail just because we can't fetch the EOL server version.
+ return FALLBACK_END_OF_LIFE_SERVER_VERSION;
+ });
+ }
+ // Return a cached or in-flight value
+ return latestEndOfLifeServerVersion;
+}
+
+export function isEndOfLifeVersion(
+ version: string,
+ latestEndOfLifeServerVersion: string
+) {
+ try {
+ const coercedVersion = semverCoerce(version);
+ return coercedVersion
+ ? semverSatisfies(coercedVersion, `<=${latestEndOfLifeServerVersion}`)
+ : false;
+ } catch (error) {
+ debug('Error comparing versions', { error });
+ // If the version is not a valid semver, we can't reliably determine if it's EOL
+ return false;
+ }
+}
diff --git a/packages/compass-telemetry/src/telemetry-events.ts b/packages/compass-telemetry/src/telemetry-events.ts
index 91e53ba04e0..3a5182ac913 100644
--- a/packages/compass-telemetry/src/telemetry-events.ts
+++ b/packages/compass-telemetry/src/telemetry-events.ts
@@ -2653,7 +2653,8 @@ type ScreenEvent = ConnectionScopedEvent<{
| 'restore_pipeline_modal'
| 'save_pipeline_modal'
| 'shell_info_modal'
- | 'update_search_index_modal';
+ | 'update_search_index_modal'
+ | 'end_of_life_mongodb_modal';
};
}>;
diff --git a/packages/data-service/src/instance-detail-helper.ts b/packages/data-service/src/instance-detail-helper.ts
index 2040d89aedd..ce84cf7396f 100644
--- a/packages/data-service/src/instance-detail-helper.ts
+++ b/packages/data-service/src/instance-detail-helper.ts
@@ -350,7 +350,9 @@ function adaptHostInfo(rawHostInfo: Partial): HostInfoDetails {
};
}
-function adaptBuildInfo(rawBuildInfo: Partial) {
+export function adaptBuildInfo(
+ rawBuildInfo: Partial
+): BuildInfoDetails {
return {
version: rawBuildInfo.version ?? '',
// Cover both cases of detecting enterprise module, see SERVER-18099.