Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin'
Browse files Browse the repository at this point in the history
  • Loading branch information
kmruiz committed Nov 29, 2023
2 parents 77ed970 + 85488c7 commit 2e9bafb
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 15 deletions.
2 changes: 1 addition & 1 deletion THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
The following third-party software is used by and included in **mongosh**.
This document was automatically generated on Mon Nov 27 2023.
This document was automatically generated on Tue Nov 28 2023.

## List of dependencies

Expand Down
36 changes: 36 additions & 0 deletions packages/cli-repl/src/cli-repl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ describe('CliRepl', function () {
};

beforeEach(async function () {
process.env.MONGOSH_ANALYTICS_SAMPLE = 'true';
requests = [];
srv = http
.createServer((req, res) => {
Expand Down Expand Up @@ -1236,6 +1237,7 @@ describe('CliRepl', function () {
});

afterEach(async function () {
delete process.env.MONGOSH_ANALYTICS_SAMPLE;
srv.close();
await once(srv, 'close');
setTelemetryDelay(0);
Expand Down Expand Up @@ -1271,6 +1273,40 @@ describe('CliRepl', function () {
expect(requests[0].body).to.include(process.platform);
});

it('posts analytics if the environment variable MONGOSH_ANALYTICS_SAMPLE is provided', async function () {
process.env.MONGOSH_ANALYTICS_SAMPLE = 'true';
await cliRepl.start(await testServer.connectionString(), {});
input.write('use somedb;\n');
await waitEval(cliRepl.bus);
// There are warnings generated by the driver if exit is used to close
// the REPL too early. That might be worth investigating at some point.
await delay(100);
input.write('exit\n');
await waitBus(cliRepl.bus, 'mongosh:closed');
const useEvents = requests.flatMap((req) =>
JSON.parse(req.body).batch.filter((entry) => entry.event === 'Use')
);
expect(useEvents).to.have.lengthOf(1);
});

it('does not post analytics if the environment variable MONGOSH_ANALYTICS_SAMPLE is true but user disabled telemetry', async function () {
process.env.MONGOSH_ANALYTICS_SAMPLE = 'true';
await cliRepl.start(await testServer.connectionString(), {});
input.write('disableTelemetry()\n');
await waitEval(cliRepl.bus);
input.write('use somedb;\n');
await waitEval(cliRepl.bus);
// There are warnings generated by the driver if exit is used to close
// the REPL too early. That might be worth investigating at some point.
await delay(100);
input.write('exit\n');
await waitBus(cliRepl.bus, 'mongosh:closed');
const useEvents = requests.flatMap((req) =>
JSON.parse(req.body).batch.filter((entry) => entry.event === 'Use')
);
expect(useEvents).to.have.lengthOf(0);
});

it('stops posting analytics data after disableTelemetry()', async function () {
await cliRepl.start(await testServer.connectionString(), {});
input.write('use somedb;\n');
Expand Down
17 changes: 11 additions & 6 deletions packages/cli-repl/src/cli-repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
setupLoggerAndTelemetry,
ToggleableAnalytics,
ThrottledAnalytics,
SampledAnalytics,
} from '@mongosh/logging';
import type { MongoshBus } from '@mongosh/types';
import { CliUserConfig, CliUserConfigValidator } from '@mongosh/types';
Expand Down Expand Up @@ -500,12 +501,16 @@ export class CliRepl implements MongoshIOProvider {
} as any /* axiosConfig and axiosRetryConfig are existing options, but don't have type definitions */
);
this.toggleableAnalytics = new ToggleableAnalytics(
new ThrottledAnalytics({
target: this.segmentAnalytics,
throttle: {
rate: 30,
metadataPath: this.shellHomeDirectory.paths.shellLocalDataPath,
},
new SampledAnalytics({
target: new ThrottledAnalytics({
target: this.segmentAnalytics,
throttle: {
rate: 30,
metadataPath: this.shellHomeDirectory.paths.shellLocalDataPath,
},
}),
sampling: () =>
!!process.env.MONGOSH_ANALYTICS_SAMPLE || Math.random() <= 0.01,
})
);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/cli-repl/src/mongosh-repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@ class MongoshNodeRepl implements EvaluationListener {
// https://github.com/mongodb/mongo/blob/a6df396047a77b90bf1ce9463eecffbee16fb864/src/mongo/shell/mongo_main.cpp#L1003-L1026
const { shellApi } = instanceState;
const banners = await Promise.all([
(async () => await shellApi.show('startupWarnings'))(),
(async () => await shellApi.show('automationNotices'))(),
(async () => await shellApi.show('nonGenuineMongoDBCheck'))(),
(async () => await shellApi._untrackedShow('startupWarnings'))(),
(async () => await shellApi._untrackedShow('automationNotices'))(),
(async () => await shellApi._untrackedShow('nonGenuineMongoDBCheck'))(),
]);
for (const banner of banners) {
if (banner.value) {
Expand Down
1 change: 1 addition & 0 deletions packages/logging/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Sergey Petushkov <[email protected]>
Leonardo Rossi <[email protected]>
Le Roux Bodenstein <[email protected]>
Martin Rodriguez Reboredo <[email protected]>
Kevin Mas Ruiz <[email protected]>
44 changes: 43 additions & 1 deletion packages/logging/src/analytics-helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import fs from 'fs';
import { promisify } from 'util';
import { expect } from 'chai';
import type { MongoshAnalytics } from './analytics-helpers';
import { ToggleableAnalytics, ThrottledAnalytics } from './analytics-helpers';
import {
ToggleableAnalytics,
ThrottledAnalytics,
SampledAnalytics,
} from './analytics-helpers';

const wait = promisify(setTimeout);

Expand Down Expand Up @@ -245,4 +249,42 @@ describe('analytics helpers', function () {
).to.match(/^(hi,hi,hi|bye,bye,bye)$/);
});
});

describe('SampledAnalytics', function () {
const userId = `u-${Date.now()}`;
const iEvt = { userId, traits: { platform: 'what', session_id: 'abc' } };
const tEvt = {
userId,
event: 'hi',
properties: { mongosh_version: '1.2.3', session_id: 'abc' },
};

it('should send the event forward when sampled', function () {
const analytics = new SampledAnalytics({
target,
sampling: () => true,
});

expect(analytics.enabled).to.be.true;

analytics.identify(iEvt);
analytics.track(tEvt);

expect(events.length).to.equal(2);
});

it('should not send the event forward when not sampled', function () {
const analytics = new SampledAnalytics({
target,
sampling: () => false,
});

expect(analytics.enabled).to.be.false;

analytics.identify(iEvt);
analytics.track(tEvt);

expect(events.length).to.equal(0);
});
});
});
31 changes: 31 additions & 0 deletions packages/logging/src/analytics-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,34 @@ export class ThrottledAnalytics implements MongoshAnalytics {
});
}
}

type SampledAnalyticsOptions = {
target?: MongoshAnalytics;
sampling: () => boolean;
};

export class SampledAnalytics implements MongoshAnalytics {
private isEnabled: boolean;
private target: MongoshAnalytics;

constructor(configuration: SampledAnalyticsOptions) {
this.isEnabled = configuration.sampling();
this.target = configuration.target || new NoopAnalytics();
}

get enabled(): boolean {
return this.isEnabled;
}

identify(message: AnalyticsIdentifyMessage): void {
this.isEnabled && this.target.identify(message);
}

track(message: AnalyticsTrackMessage): void {
this.isEnabled && this.target.track(message);
}

flush(callback: (err?: Error | undefined) => void): void {
this.target.flush(callback);
}
}
1 change: 1 addition & 0 deletions packages/logging/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { setupLoggerAndTelemetry } from './setup-logger-and-telemetry';
export {
MongoshAnalytics,
ToggleableAnalytics,
SampledAnalytics,
NoopAnalytics,
ThrottledAnalytics,
} from './analytics-helpers';
18 changes: 18 additions & 0 deletions packages/shell-api/src/mongo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ describe('Mongo', function () {
instanceState.currentDb = database;
});
describe('show', function () {
it('should send telemetry by default', async function () {
serviceProvider.listDatabases.resolves({ ok: 1, databases: [] });
await mongo.show('dbs');

expect(bus.emit).to.have.been.calledWith('mongosh:show', {
method: 'show dbs',
});
});

it('should not send telemetry when disabled', async function () {
serviceProvider.listDatabases.resolves({ ok: 1, databases: [] });
await mongo.show('dbs', undefined, false);

expect(bus.emit).to.not.have.been.calledWith('mongosh:show', {
method: 'show dbs',
});
});

['databases', 'dbs'].forEach((t) => {
describe(t, function () {
it('calls serviceProvider.listDatabases on the admin database', async function () {
Expand Down
14 changes: 10 additions & 4 deletions packages/shell-api/src/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,13 +363,19 @@ export default class Mongo extends ShellApiClass {

@returnsPromise
@apiVersions([1])
async show(cmd: string, arg?: string): Promise<CommandResult> {
async show(
cmd: string,
arg?: string,
tracked = true
): Promise<CommandResult> {
const db = this._instanceState.currentDb;
// legacy shell:
// https://github.com/mongodb/mongo/blob/a6df396047a77b90bf1ce9463eecffbee16fb864/src/mongo/shell/utils.js#L900-L1226
this._instanceState.messageBus.emit('mongosh:show', {
method: `show ${cmd}`,
});

tracked &&
this._instanceState.messageBus.emit('mongosh:show', {
method: `show ${cmd}`,
});

switch (cmd) {
case 'databases':
Expand Down
12 changes: 12 additions & 0 deletions packages/shell-api/src/shell-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,18 @@ describe('ShellApi', function () {
expect(mongo.show).to.have.been.calledWith('databases');
});
});
describe('_untrackedShow', function () {
beforeEach(async function () {
await instanceState.shellApi._untrackedShow('databases');
});
it('calls show with arg and without telemetry', function () {
expect(mongo.show).to.have.been.calledWith(
'databases',
undefined,
false
);
});
});
describe('it', function () {
it('returns empty result if no current cursor', async function () {
instanceState.currentCursor = null;
Expand Down
7 changes: 7 additions & 0 deletions packages/shell-api/src/shell-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ export default class ShellApi extends ShellApiClass {
return await this._instanceState.currentDb._mongo.show(cmd, arg);
}

@directShellCommand
@returnsPromise
@shellCommandCompleter(showCompleter)
async _untrackedShow(cmd: string, arg?: string): Promise<CommandResult> {
return await this._instanceState.currentDb._mongo.show(cmd, arg, false);
}

@directShellCommand
@returnsPromise
@platforms(['CLI'])
Expand Down

0 comments on commit 2e9bafb

Please sign in to comment.