Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 19 additions & 1 deletion packages/compass-schema/src/stores/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Schema } from 'mongodb-schema';
import type { Action, AnyAction, Reducer } from 'redux';
import ConnectionString from 'mongodb-connection-string-url';
import type { AggregateOptions } from 'mongodb';
import { type AnalysisState } from '../constants/analysis-states';
import {
ANALYSIS_STATE_ANALYZING,
Expand Down Expand Up @@ -212,6 +214,12 @@ export const stopAnalysis = (): SchemaThunkAction<void> => {
};
};

function isReadPreferenceSet(connectionString: string): boolean {
return !!new ConnectionString(connectionString).searchParams.get(
'readPreference'
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a strong preference, but I feel like dataService might be a better place for this logic, it feels out of the domain of the schema plugin to know how to extract connection options from the connection info string.

Maybe we can make this an interface on the sample method? Something like fallbackReadPreference or defaultReadPreference option that we can pass to the method and will be only applied by the sample method using the same logic you implemented here? After all dataService is there to abstract away some parts of interacting with the driver for feature code


export const startAnalysis = (): SchemaThunkAction<
Promise<void>,
AnalysisStartedAction | AnalysisFinishedAction | AnalysisFailedAction
Expand Down Expand Up @@ -250,8 +258,18 @@ export const startAnalysis = (): SchemaThunkAction<
fields: query.project ?? undefined,
};

const driverOptions = {
const driverOptions: AggregateOptions = {
maxTimeMS: capMaxTimeMSAtPreferenceLimit(preferences, query.maxTimeMS),
// When the read preference isn't set in the connection string explicitly,
// then we default to secondaryPreferred to avoid using the primary for
// schema analysis.
...(isReadPreferenceSet(
connectionInfoRef.current.connectionOptions.connectionString
)
? {}
: {
readPreference: 'secondaryPreferred',
}),
};

try {
Expand Down
125 changes: 86 additions & 39 deletions packages/compass-schema/src/stores/store.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { activateSchemaPlugin, type SchemaStore } from './store';
import { activateSchemaPlugin } from './store';
import type { SchemaStore, SchemaPluginServices } from './store';
import AppRegistry, { createActivateHelpers } from 'hadron-app-registry';
import { expect } from 'chai';

Expand All @@ -7,7 +8,6 @@ import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
import { createNoopLogger } from '@mongodb-js/compass-logging/provider';
import type { FieldStoreService } from '@mongodb-js/compass-field-store';
import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider';
import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider';
import { startAnalysis, stopAnalysis } from './reducer';
import Sinon from 'sinon';

Expand All @@ -26,46 +26,61 @@ const mockQueryBar = {
};

describe('Schema Store', function () {
describe('#configureStore', function () {
let store: SchemaStore;
let deactivate: () => void;
let sandbox: Sinon.SinonSandbox;
const localAppRegistry = new AppRegistry();
const globalAppRegistry = new AppRegistry();
const namespace = 'db.coll';
let store: SchemaStore;
let deactivate: () => void;
let sandbox: Sinon.SinonSandbox;
const localAppRegistry = new AppRegistry();
const globalAppRegistry = new AppRegistry();
const namespace = 'db.coll';
let sampleStub: Sinon.SinonStub;
let isCancelErrorStub: Sinon.SinonStub;

beforeEach(function () {
sandbox = Sinon.createSandbox();
sampleStub = sandbox.stub();
isCancelErrorStub = sandbox.stub();
});

async function createStore(services: Partial<SchemaPluginServices> = {}) {
const dataService = {
sample: sampleStub,
isCancelError: isCancelErrorStub,
};
const connectionInfoRef = {
current: {},
} as ConnectionInfoRef;
let sampleStub: Sinon.SinonStub;
let isCancelErrorStub: Sinon.SinonStub;
current: {
connectionOptions: {
connectionString: 'mongodb://localhost:27017',
},
title: 'test',
id: 'test',
},
};

const plugin = activateSchemaPlugin(
{
namespace: namespace,
},
{
localAppRegistry: localAppRegistry,
globalAppRegistry: globalAppRegistry,
dataService,
logger: dummyLogger,
track: dummyTrack,
preferences: await createSandboxFromDefaultPreferences(),
fieldStoreService: mockFieldStoreService,
queryBar: mockQueryBar as any,
connectionInfoRef,
...services,
},
createActivateHelpers()
);
store = plugin.store;
deactivate = () => plugin.deactivate();
}

describe('#configureStore', function () {
beforeEach(async function () {
sandbox = Sinon.createSandbox();
sampleStub = sandbox.stub();
isCancelErrorStub = sandbox.stub();
const dataService = {
sample: sampleStub,
isCancelError: isCancelErrorStub,
};
const plugin = activateSchemaPlugin(
{
namespace: namespace,
},
{
localAppRegistry: localAppRegistry,
globalAppRegistry: globalAppRegistry,
dataService: dataService as any,
logger: dummyLogger,
track: dummyTrack,
preferences: await createSandboxFromDefaultPreferences(),
fieldStoreService: mockFieldStoreService,
queryBar: mockQueryBar as any,
connectionInfoRef,
},
createActivateHelpers()
);
store = plugin.store;
deactivate = () => plugin.deactivate();
await createStore();
});

afterEach(function () {
Expand Down Expand Up @@ -107,5 +122,37 @@ describe('Schema Store', function () {
await analysisPromise;
expect(store.getState().analysisState).to.equal('initial');
});

it('runs the analysis with read pref secondaryPreferred', async function () {
sampleStub.resolves([{ name: 'Hans' }, { name: 'Greta' }]);
await store.dispatch(startAnalysis());
expect(sampleStub.getCall(0).args[2])
.property('readPreference')
.to.equal('secondaryPreferred');
});
});

describe('with a connection string with explicit read preference set', function () {
beforeEach(async function () {
await createStore({
connectionInfoRef: {
current: {
connectionOptions: {
connectionString:
'mongodb://localhost:27017/?readPreference=primary',
},
title: 'test',
id: 'test',
},
},
});
});

it('does not set read preference to secondaryPreferred', async function () {
await store.dispatch(startAnalysis());
expect(sampleStub.getCall(0).args[2]).not.to.have.property(
'readPreference'
);
});
});
});
Loading