diff --git a/packages/compass-home/src/components/home.tsx b/packages/compass-home/src/components/home.tsx
index 67ecd5285eb..077f8faff97 100644
--- a/packages/compass-home/src/components/home.tsx
+++ b/packages/compass-home/src/components/home.tsx
@@ -246,7 +246,7 @@ function Home({ appName }: { appName: string }): React.ReactElement | null {
return (
);
diff --git a/packages/connections/src/components/connections.spec.tsx b/packages/connections/src/components/connections.spec.tsx
index 1088b9374e9..1fa54a21e61 100644
--- a/packages/connections/src/components/connections.spec.tsx
+++ b/packages/connections/src/components/connections.spec.tsx
@@ -23,19 +23,20 @@ function getMockConnectionStorage(
return Promise.resolve(mockConnections);
},
save: () => Promise.resolve(),
+ delete: () => Promise.resolve(),
};
}
-async function loadSavedConnectionAndConnect(savedConnectionId: string) {
+async function loadSavedConnectionAndConnect(connectionInfo: ConnectionInfo) {
const savedConnectionButton = screen.getByTestId(
- `saved-connection-button-${savedConnectionId}`
+ `saved-connection-button-${connectionInfo.id}`
);
fireEvent.click(savedConnectionButton);
// Wait for the connection to load in the form.
await waitFor(() =>
expect(screen.queryByRole('textbox').textContent).to.equal(
- 'mongodb://localhost:27018/?readPreference=primary&ssl=false'
+ connectionInfo.connectionOptions.connectionString
)
);
@@ -51,13 +52,9 @@ describe('Connections Component', function () {
beforeEach(function () {
onConnectedSpy = sinon.spy();
- this.clock = sinon.useFakeTimers({
- now: 1483228800000,
- });
});
afterEach(function () {
- this.clock.restore();
sinon.restore();
cleanup();
});
@@ -72,6 +69,7 @@ describe('Connections Component', function () {
);
});
@@ -117,16 +115,19 @@ describe('Connections Component', function () {
let mockConnectFn: sinon.SinonSpy;
let mockStorage: ConnectionStore;
let savedConnectionId: string;
+ let savedConnectionWithAppNameId: string;
let saveConnectionSpy: sinon.SinonSpy;
+ let connections: ConnectionInfo[];
beforeEach(async function () {
mockConnectFn = sinon.fake.resolves({
mockDataService: 'yes',
});
savedConnectionId = uuid();
+ savedConnectionWithAppNameId = uuid();
saveConnectionSpy = sinon.spy();
- mockStorage = getMockConnectionStorage([
+ connections = [
{
id: savedConnectionId,
connectionOptions: {
@@ -134,7 +135,15 @@ describe('Connections Component', function () {
'mongodb://localhost:27018/?readPreference=primary&ssl=false',
},
},
- ]);
+ {
+ id: savedConnectionWithAppNameId,
+ connectionOptions: {
+ connectionString:
+ 'mongodb://localhost:27019/?appName=Some+App+Name',
+ },
+ },
+ ];
+ mockStorage = getMockConnectionStorage(connections);
sinon.replace(mockStorage, 'save', saveConnectionSpy);
render(
@@ -142,21 +151,22 @@ describe('Connections Component', function () {
onConnected={onConnectedSpy}
connectFn={mockConnectFn}
connectionStorage={mockStorage}
+ appName="Test App Name"
/>
);
- await waitFor(() => expect(screen.queryByRole('listitem')).to.be.visible);
+ await waitFor(() => expect(screen.queryAllByRole('listitem')).to.exist);
});
it('should render the saved connections', function () {
const listItems = screen.getAllByRole('listitem');
- expect(listItems.length).to.equal(1);
+ expect(listItems.length).to.equal(2);
const favorites = screen.queryAllByTestId('favorite-connection');
expect(favorites.length).to.equal(0);
const recents = screen.getAllByTestId('recent-connection');
- expect(recents.length).to.equal(1);
+ expect(recents.length).to.equal(2);
});
it('renders the title of the saved connection', function () {
@@ -169,7 +179,9 @@ describe('Connections Component', function () {
throw new Error('Error: pineapples');
};
- await loadSavedConnectionAndConnect(savedConnectionId);
+ await loadSavedConnectionAndConnect(
+ connections.find(({ id }) => id === savedConnectionId)
+ );
});
it('displays the error that occurred when saving', function () {
@@ -194,14 +206,23 @@ describe('Connections Component', function () {
describe('when a saved connection is clicked on and connected to', function () {
beforeEach(async function () {
- await loadSavedConnectionAndConnect(savedConnectionId);
+ this.clock = sinon.useFakeTimers({
+ now: 1483228800000,
+ });
+ await loadSavedConnectionAndConnect(
+ connections.find(({ id }) => id === savedConnectionId)
+ );
+ });
+
+ afterEach(function () {
+ this.clock.restore();
});
it('should call the connect function with the connection options to connect', function () {
expect(mockConnectFn.callCount).to.equal(1);
expect(mockConnectFn.firstCall.args[0]).to.deep.equal({
connectionString:
- 'mongodb://localhost:27018/?readPreference=primary&ssl=false',
+ 'mongodb://localhost:27018/?readPreference=primary&ssl=false&appName=Test+App+Name',
});
});
@@ -241,6 +262,21 @@ describe('Connections Component', function () {
);
});
});
+
+ describe('when a saved connection with appName is clicked on and connected to', function () {
+ beforeEach(async function () {
+ await loadSavedConnectionAndConnect(
+ connections.find(({ id }) => id === savedConnectionWithAppNameId)
+ );
+ });
+
+ it('should call the connect function without replacing appName', function () {
+ expect(mockConnectFn.callCount).to.equal(1);
+ expect(mockConnectFn.firstCall.args[0]).to.deep.equal({
+ connectionString: 'mongodb://localhost:27019/?appName=Some+App+Name',
+ });
+ });
+ });
});
describe('connecting to a connection that is not succeeding', function () {
@@ -248,6 +284,7 @@ describe('Connections Component', function () {
let saveConnectionSpy: sinon.SinonSpy;
let savedConnectableId: string;
let savedUnconnectableId: string;
+ let connections: ConnectionInfo[];
beforeEach(async function () {
saveConnectionSpy = sinon.spy();
@@ -258,7 +295,7 @@ describe('Connections Component', function () {
async (connectionOptions: ConnectionOptions) => {
if (
connectionOptions.connectionString ===
- 'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000'
+ 'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000&appName=Test+App+Name'
) {
return new Promise((resolve) => {
// On first call we want this attempt to be cancelled before
@@ -272,7 +309,7 @@ describe('Connections Component', function () {
}
);
- const mockStorage = getMockConnectionStorage([
+ connections = [
{
id: savedConnectableId,
connectionOptions: {
@@ -287,7 +324,8 @@ describe('Connections Component', function () {
'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000',
},
},
- ]);
+ ];
+ const mockStorage = getMockConnectionStorage(connections);
sinon.replace(mockStorage, 'save', saveConnectionSpy);
render(
@@ -295,6 +333,7 @@ describe('Connections Component', function () {
onConnected={onConnectedSpy}
connectFn={mockConnectFn}
connectionStorage={mockStorage}
+ appName="Test App Name"
/>
);
@@ -322,16 +361,13 @@ describe('Connections Component', function () {
const connectButton = screen.getByText('Connect');
fireEvent.click(connectButton);
- // Speedup the modal showing animation.
- this.clock.tick(300);
-
// Wait for the connecting... modal to be shown.
await waitFor(() => expect(screen.queryByText('Cancel')).to.be.visible);
});
describe('when the connection attempt is cancelled', function () {
beforeEach(async function () {
- const cancelButton = screen.getByText('Cancel').closest('Button');
+ const cancelButton = screen.getByText('Cancel');
fireEvent.click(cancelButton);
// Wait for the connecting... modal to hide.
@@ -359,13 +395,15 @@ describe('Connections Component', function () {
expect(mockConnectFn.callCount).to.equal(1);
expect(mockConnectFn.firstCall.args[0]).to.deep.equal({
connectionString:
- 'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000',
+ 'mongodb://localhost:27099/?connectTimeoutMS=5000&serverSelectionTimeoutMS=5000&appName=Test+App+Name',
});
});
describe('connecting to a successful connection after cancelling a connect', function () {
beforeEach(async function () {
- await loadSavedConnectionAndConnect(savedConnectableId);
+ await loadSavedConnectionAndConnect(
+ connections.find(({ id }) => id === savedConnectableId)
+ );
});
it('should call onConnected once', function () {
@@ -392,7 +430,7 @@ describe('Connections Component', function () {
expect(mockConnectFn.callCount).to.equal(2);
expect(mockConnectFn.secondCall.args[0]).to.deep.equal({
connectionString:
- 'mongodb://localhost:27018/?readPreference=primary&ssl=false',
+ 'mongodb://localhost:27018/?readPreference=primary&ssl=false&appName=Test+App+Name',
});
});
diff --git a/packages/connections/src/components/connections.tsx b/packages/connections/src/components/connections.tsx
index 1c45dfdad38..46b0967ab1c 100644
--- a/packages/connections/src/components/connections.tsx
+++ b/packages/connections/src/components/connections.tsx
@@ -57,6 +57,7 @@ const formContainerStyles = css({
function Connections({
onConnected,
connectionStorage = new ConnectionStorage(),
+ appName,
connectFn = connect,
}: {
onConnected: (
@@ -64,6 +65,7 @@ function Connections({
dataService: DataService
) => void;
connectionStorage?: ConnectionStore;
+ appName: string;
connectFn?: (connectionOptions: ConnectionOptions) => Promise;
}): React.ReactElement {
const {
@@ -77,7 +79,7 @@ function Connections({
removeAllRecentsConnections,
removeConnection,
saveConnection,
- } = useConnections(onConnected, connectionStorage, connectFn);
+ } = useConnections({ onConnected, connectionStorage, connectFn, appName });
const {
activeConnectionId,
activeConnectionInfo,
diff --git a/packages/connections/src/stores/connections-store.spec.ts b/packages/connections/src/stores/connections-store.spec.ts
index 7f9b7ef75cc..cb1aa43b74c 100644
--- a/packages/connections/src/stores/connections-store.spec.ts
+++ b/packages/connections/src/stores/connections-store.spec.ts
@@ -56,7 +56,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = loadAllSpyWithData;
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
// Wait for the async loading of connections to complete.
@@ -76,7 +81,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = () => Promise.resolve(mockConnections);
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
// Wait for the async loading of connections to complete.
@@ -130,7 +140,12 @@ describe('use-connections hook', function () {
let hookResult: RenderResult>;
beforeEach(async function () {
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
await act(async () => {
@@ -172,7 +187,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = () => Promise.resolve(mockConnections);
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
// Wait for the async loading of connections to complete.
@@ -256,7 +276,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = loadAllSpyWithData;
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
await waitFor(() => {
expect(result.current.state.connections.length).to.equal(3);
@@ -278,7 +303,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = loadAllSpyWithData;
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
await waitFor(() => {
expect(result.current.state.connections.length).to.equal(2);
@@ -321,7 +351,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = loadAllSpyWithData;
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
await waitFor(() => {
expect(result.current.state.connections.length).to.equal(2);
@@ -374,7 +409,12 @@ describe('use-connections hook', function () {
mockConnectionStorage.loadAll = loadAllSpyWithData;
const { result } = renderHook(() =>
- useConnections(noop, mockConnectionStorage, noop)
+ useConnections({
+ onConnected: noop,
+ connectionStorage: mockConnectionStorage,
+ connectFn: noop,
+ appName: 'Test App Name',
+ })
);
await waitFor(() => {
expect(result.current.state.connections.length).to.equal(2);
diff --git a/packages/connections/src/stores/connections-store.ts b/packages/connections/src/stores/connections-store.ts
index 70137ac51cd..293a13ee0f7 100644
--- a/packages/connections/src/stores/connections-store.ts
+++ b/packages/connections/src/stores/connections-store.ts
@@ -16,6 +16,9 @@ import {
trackNewConnectionEvent,
trackConnectionFailedEvent,
} from '../modules/telemetry';
+import ConnectionString from 'mongodb-connection-string-url';
+import type { MongoClientOptions } from 'mongodb';
+
const debug = debugModule('mongodb-compass:connections:connections-store');
export function createNewConnectionInfo(): ConnectionInfo {
@@ -26,13 +29,37 @@ export function createNewConnectionInfo(): ConnectionInfo {
},
};
}
-
export interface ConnectionStore {
loadAll: () => Promise;
save: (connectionInfo: ConnectionInfo) => Promise;
delete: (connectionInfo: ConnectionInfo) => Promise;
}
+function setAppNameParamIfMissing(
+ connectionString: string,
+ appName: string
+): string {
+ let connectionStringUrl;
+
+ try {
+ connectionStringUrl = new ConnectionString(connectionString);
+ } catch (e) {
+ //
+ }
+
+ if (!connectionStringUrl) {
+ return connectionString;
+ }
+
+ const searchParams =
+ connectionStringUrl.typedSearchParams();
+ if (!searchParams.has('appName')) {
+ searchParams.set('appName', appName);
+ }
+
+ return connectionStringUrl.href;
+}
+
type State = {
activeConnectionId?: string;
activeConnectionInfo: ConnectionInfo;
@@ -184,14 +211,20 @@ async function loadConnections(
}
}
-export function useConnections(
+export function useConnections({
+ onConnected,
+ connectionStorage,
+ appName,
+ connectFn,
+}: {
onConnected: (
connectionInfo: ConnectionInfo,
dataService: DataService
- ) => void,
- connectionStorage: ConnectionStore,
- connectFn: (connectionOptions: ConnectionOptions) => Promise
-): {
+ ) => void;
+ connectionStorage: ConnectionStore;
+ connectFn: (connectionOptions: ConnectionOptions) => Promise;
+ appName: string;
+}): {
state: State;
cancelConnectionAttempt: () => void;
connect: (connectionInfo: ConnectionInfo) => Promise;
@@ -318,9 +351,14 @@ export function useConnections(
debug('connecting with connectionInfo', connectionInfo);
try {
- const newConnectionDataService = await newConnectionAttempt.connect(
- connectionInfo.connectionOptions
+ const connectionStringWithAppName = setAppNameParamIfMissing(
+ connectionInfo.connectionOptions.connectionString,
+ appName
);
+ const newConnectionDataService = await newConnectionAttempt.connect({
+ ...connectionInfo.connectionOptions,
+ connectionString: connectionStringWithAppName,
+ });
connectingConnectionAttempt.current = undefined;
if (!newConnectionDataService || newConnectionAttempt.isClosed()) {
diff --git a/packages/data-service/src/legacy/legacy-connection-model.spec.ts b/packages/data-service/src/legacy/legacy-connection-model.spec.ts
index 70174753ff9..ee7a99f40f5 100644
--- a/packages/data-service/src/legacy/legacy-connection-model.spec.ts
+++ b/packages/data-service/src/legacy/legacy-connection-model.spec.ts
@@ -1,4 +1,5 @@
import { expect } from 'chai';
+import ConnectionString from 'mongodb-connection-string-url';
import util from 'util';
import type { ConnectionInfo } from '../connection-info';
@@ -23,7 +24,7 @@ async function createAndConvertModel(
describe('LegacyConnectionModel', function () {
describe('convertConnectionModelToInfo', function () {
- it('converts a raw model to the connection model instance', function () {
+ it('converts a raw model to connection info', function () {
const rawModel = {
_id: '1234-1234-1234-1234',
hostname: 'localhost',
@@ -38,6 +39,32 @@ describe('LegacyConnectionModel', function () {
expect(id).to.deep.equal('1234-1234-1234-1234');
});
+ it('removes appName if matches MongoDB Compass', async function () {
+ const { connectionOptions } = await createAndConvertModel(
+ 'mongodb://localhost:27017/admin?appName=MongoDB+Compass',
+ { _id: '1234-1234-1234-1234' }
+ );
+
+ expect(
+ new ConnectionString(
+ connectionOptions.connectionString
+ ).searchParams.has('appName')
+ ).to.be.false;
+ });
+
+ it('preserves appName if does not match MongoDB Compass', async function () {
+ const { connectionOptions } = await createAndConvertModel(
+ 'mongodb://localhost:27017/admin?appName=Some+Other+App',
+ { _id: '1234-1234-1234-1234' }
+ );
+
+ expect(
+ new ConnectionString(
+ connectionOptions.connectionString
+ ).searchParams.get('appName')
+ ).to.deep.equal('Some Other App');
+ });
+
it('converts _id', async function () {
const { id } = await createAndConvertModel(
'mongodb://localhost:27017/admin',
diff --git a/packages/data-service/src/legacy/legacy-connection-model.ts b/packages/data-service/src/legacy/legacy-connection-model.ts
index 6e6259b5fcf..2906f85143c 100644
--- a/packages/data-service/src/legacy/legacy-connection-model.ts
+++ b/packages/data-service/src/legacy/legacy-connection-model.ts
@@ -26,6 +26,36 @@ type SslMethod =
| 'SERVER'
| 'ALL';
+function deleteCompassAppNameParam(
+ connectionInfo: ConnectionInfo
+): ConnectionInfo {
+ let connectionStringUrl;
+
+ try {
+ connectionStringUrl = new ConnectionString(
+ connectionInfo.connectionOptions.connectionString
+ );
+ } catch {
+ return connectionInfo;
+ }
+
+ if (
+ /^mongodb compass/i.exec(
+ connectionStringUrl.searchParams.get('appName') || ''
+ )
+ ) {
+ connectionStringUrl.searchParams.delete('appName');
+ }
+
+ return {
+ ...connectionInfo,
+ connectionOptions: {
+ ...connectionInfo.connectionOptions,
+ connectionString: connectionStringUrl.href,
+ },
+ };
+}
+
export interface LegacyConnectionModelProperties {
_id: string;
hostname: string;
@@ -173,7 +203,7 @@ export function convertConnectionModelToInfo(
connectionInfo.lastUsed = new Date(connectionInfo.lastUsed);
}
- return connectionInfo;
+ return deleteCompassAppNameParam(connectionInfo);
}
// Not migrated yet, has to be converted
@@ -223,7 +253,7 @@ export function convertConnectionModelToInfo(
info.lastUsed = legacyModel.lastUsed;
}
- return info;
+ return deleteCompassAppNameParam(info);
}
function setConnectionStringParam(