Skip to content

Commit bb2885d

Browse files
authored
fix: send atlas uid as userId to compass telemetry COMPASS-7610 (#5441)
1 parent 86bded5 commit bb2885d

File tree

8 files changed

+127
-30
lines changed

8 files changed

+127
-30
lines changed

packages/atlas-service/src/main.spec.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import Sinon from 'sinon';
22
import { expect } from 'chai';
3-
import { AtlasService, getTrackingUserInfo, throwIfNotOk } from './main';
3+
import { AtlasService, throwIfNotOk } from './main';
4+
import * as util from './util';
45
import { EventEmitter } from 'events';
56
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
67
import type { PreferencesAccess } from 'compass-preferences-model';
78
import type { AtlasUserConfigStore } from './user-config-store';
8-
import type { AtlasUserInfo } from './util';
99

1010
function getListenerCount(emitter: EventEmitter) {
1111
return emitter.eventNames().reduce((acc, name) => {
@@ -74,6 +74,14 @@ describe('AtlasServiceMain', function () {
7474

7575
let preferences: PreferencesAccess;
7676

77+
let getTrackingUserInfoStub: Sinon.SinonStubbedMember<
78+
typeof util.getTrackingUserInfo
79+
>;
80+
81+
before(function () {
82+
getTrackingUserInfoStub = sandbox.stub(util, 'getTrackingUserInfo');
83+
});
84+
7785
beforeEach(async function () {
7886
AtlasService['ipcMain'] = {
7987
handle: sandbox.stub(),
@@ -114,8 +122,15 @@ describe('AtlasServiceMain', function () {
114122
sandbox.resetHistory();
115123
});
116124

125+
after(function () {
126+
sandbox.restore();
127+
});
128+
117129
describe('signIn', function () {
118130
it('should sign in using oidc plugin', async function () {
131+
const atlasUid = 'abcdefgh';
132+
getTrackingUserInfoStub.returns({ auid: atlasUid });
133+
119134
const userInfo = await AtlasService.signIn();
120135
expect(
121136
mockOidcPlugin.mongoClientOptions.authMechanismProperties
@@ -124,6 +139,9 @@ describe('AtlasServiceMain', function () {
124139
// proper error message from oidc plugin in case of failed sign in
125140
).to.have.been.calledTwice;
126141
expect(userInfo).to.have.property('sub', '1234');
142+
expect(preferences.getPreferences().telemetryAtlasUserId).to.equal(
143+
atlasUid
144+
);
127145
});
128146

129147
it('should debounce inflight sign in requests', async function () {
@@ -522,19 +540,6 @@ describe('AtlasServiceMain', function () {
522540
});
523541
});
524542

525-
describe('getTrackingUserInfo', function () {
526-
it('should return required tracking info from user info', function () {
527-
expect(
528-
getTrackingUserInfo({
529-
sub: '1234',
530-
primaryEmail: '[email protected]',
531-
} as AtlasUserInfo)
532-
).to.deep.eq({
533-
auid: '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4',
534-
});
535-
});
536-
});
537-
538543
describe('setupAIAccess', function () {
539544
beforeEach(async function () {
540545
await preferences.savePreferences({

packages/atlas-service/src/main.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { shell, app } from 'electron';
22
import { URL, URLSearchParams } from 'url';
3-
import { createHash } from 'crypto';
43
import type { AuthFlowType, MongoDBOIDCPlugin } from '@mongodb-js/oidc-plugin';
5-
import { AtlasServiceError } from './util';
4+
import { AtlasServiceError, getTrackingUserInfo } from './util';
65
import {
76
createMongoDBOIDCPlugin,
87
hookLoggerToMongoLogWriter as oidcPluginHookLoggerToMongoLogWriter,
@@ -114,14 +113,6 @@ const TOKEN_TYPE_TO_HINT = {
114113
refreshToken: 'refresh_token',
115114
} as const;
116115

117-
export function getTrackingUserInfo(userInfo: AtlasUserInfo) {
118-
return {
119-
// AUID is shared Cloud user identificator that can be tracked through
120-
// various MongoDB properties
121-
auid: createHash('sha256').update(userInfo.sub, 'utf8').digest('hex'),
122-
};
123-
}
124-
125116
export type AtlasServiceConfig = {
126117
atlasApiBaseUrl: string;
127118
atlasApiUnauthBaseUrl: string;
@@ -374,7 +365,11 @@ export class AtlasService {
374365
'AtlasService',
375366
'Signed in successfully'
376367
);
377-
track('Atlas Sign In Success', getTrackingUserInfo(userInfo));
368+
const { auid } = getTrackingUserInfo(userInfo);
369+
track('Atlas Sign In Success', { auid });
370+
await this.preferences.savePreferences({
371+
telemetryAtlasUserId: auid,
372+
});
378373
return userInfo;
379374
} catch (err) {
380375
track('Atlas Sign In Error', {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getTrackingUserInfo } from './util';
2+
import type { AtlasUserInfo } from './util';
3+
import { expect } from 'chai';
4+
5+
describe('getTrackingUserInfo', function () {
6+
it('should return required tracking info from user info', function () {
7+
expect(
8+
getTrackingUserInfo({
9+
sub: '1234',
10+
primaryEmail: '[email protected]',
11+
} as AtlasUserInfo)
12+
).to.deep.eq({
13+
auid: '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4',
14+
});
15+
});
16+
});

packages/atlas-service/src/util.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type * as plugin from '@mongodb-js/oidc-plugin';
22
import util from 'util';
33
import type { AtlasUserConfig } from './user-config-store';
4+
import { createHash } from 'crypto';
45

56
export type AtlasUserInfo = {
67
sub: string;
@@ -160,3 +161,11 @@ export class AtlasServiceError extends Error {
160161
this.detail = detail;
161162
}
162163
}
164+
165+
export function getTrackingUserInfo(userInfo: AtlasUserInfo) {
166+
return {
167+
// AUID is shared Cloud user identificator that can be tracked through
168+
// various MongoDB properties
169+
auid: createHash('sha256').update(userInfo.sub, 'utf8').digest('hex'),
170+
};
171+
}

packages/compass-e2e-tests/tests/atlas-login.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { expect } from 'chai';
1414
import { createNumbersCollection } from '../helpers/insert-data';
1515
import { AcceptTOSToggle } from '../helpers/selectors';
1616
import { startMockAtlasServiceServer } from '../helpers/atlas-service';
17+
import type { Telemetry } from '../helpers/telemetry';
18+
import { startTelemetryServer } from '../helpers/telemetry';
1719

1820
const DEFAULT_TOKEN_PAYLOAD = {
1921
expires_in: 3600,
@@ -153,6 +155,48 @@ describe('Atlas Login', function () {
153155
});
154156
});
155157

158+
describe('telemetry', () => {
159+
let telemetry: Telemetry;
160+
161+
before(async function () {
162+
telemetry = await startTelemetryServer();
163+
});
164+
165+
after(async function () {
166+
await telemetry.stop();
167+
});
168+
169+
it('should send identify after the user has logged in', async function () {
170+
const atlasUserIdBefore = await browser.getFeature(
171+
'telemetryAtlasUserId'
172+
);
173+
expect(atlasUserIdBefore).to.not.exist;
174+
175+
await browser.openSettingsModal('Feature Preview');
176+
177+
await browser.clickVisible(Selectors.LogInWithAtlasButton);
178+
179+
const loginStatus = browser.$(Selectors.AtlasLoginStatus);
180+
await browser.waitUntil(async () => {
181+
return (
182+
(await loginStatus.getText()).trim() ===
183+
'Logged in with Atlas account [email protected]'
184+
);
185+
});
186+
187+
const atlasUserIdAfter = await browser.getFeature(
188+
'telemetryAtlasUserId'
189+
);
190+
expect(atlasUserIdAfter).to.be.a('string');
191+
192+
const identify = telemetry
193+
.events()
194+
.find((entry) => entry.type === 'identify');
195+
expect(identify.traits.platform).to.equal(process.platform);
196+
expect(identify.traits.arch).to.match(/^(x64|arm64)$/);
197+
});
198+
});
199+
156200
it('should allow to accept TOS when signed in', async function () {
157201
await browser.openSettingsModal('Feature Preview');
158202

packages/compass-e2e-tests/tests/logging.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,16 @@ describe('Logging and Telemetry integration', function () {
390390
});
391391
});
392392

393-
describe('on subsequent run', function () {
393+
describe('on subsequent run - with atlas user id', function () {
394394
let compass: Compass;
395395
let telemetry: Telemetry;
396+
const auid = 'abcdef';
396397

397398
before(async function () {
398399
telemetry = await startTelemetryServer();
399400
compass = await init(this.test?.fullTitle());
401+
402+
await compass.browser.setFeature('telemetryAtlasUserId', auid);
400403
});
401404

402405
afterEach(async function () {
@@ -409,8 +412,6 @@ describe('Logging and Telemetry integration', function () {
409412
});
410413

411414
it('tracks an event for identify call', function () {
412-
console.log(telemetry.events());
413-
414415
const identify = telemetry
415416
.events()
416417
.find((entry) => entry.type === 'identify');

packages/compass-preferences-model/src/preferences-schema.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export type InternalUserPreferences = {
6666
lastKnownVersion: string;
6767
currentUserId?: string;
6868
telemetryAnonymousId?: string;
69+
telemetryAtlasUserId?: string;
6970
userCreatedAt: number;
7071
};
7172

@@ -312,6 +313,17 @@ export const storedUserPreferencesProps: Required<{
312313
validator: z.string().uuid().optional(),
313314
type: 'string',
314315
},
316+
/**
317+
* Stores a unique telemetry atlas ID for the current user.
318+
*/
319+
telemetryAtlasUserId: {
320+
ui: false,
321+
cli: false,
322+
global: false,
323+
description: null,
324+
validator: z.string().optional(),
325+
type: 'string',
326+
},
315327
/**
316328
* Stores the timestamp for when the user was created
317329
*/

packages/compass/src/main/telemetry.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class CompassTelemetry {
3636
private static state: 'enabled' | 'disabled' = 'disabled';
3737
private static queuedEvents: EventInfo[] = []; // Events that happen before we fetch user preferences
3838
private static telemetryAnonymousId = ''; // The randomly generated anonymous user id.
39+
private static telemetryAtlasUserId?: string;
3940
private static lastReportedScreen = '';
4041
private static osInfo: ReturnType<typeof getOsInfo> extends Promise<infer T>
4142
? Partial<T>
@@ -79,6 +80,7 @@ class CompassTelemetry {
7980
}
8081

8182
this.analytics.track({
83+
userId: this.telemetryAtlasUserId,
8284
anonymousId: this.telemetryAnonymousId,
8385
event: info.event,
8486
properties: { ...info.properties, ...commonProperties },
@@ -105,6 +107,7 @@ class CompassTelemetry {
105107
this.telemetryAnonymousId
106108
) {
107109
this.analytics.identify({
110+
userId: this.telemetryAtlasUserId,
108111
anonymousId: this.telemetryAnonymousId,
109112
traits: {
110113
...this._getCommonProperties(),
@@ -127,9 +130,10 @@ class CompassTelemetry {
127130

128131
private static async _init(app: typeof CompassApplication) {
129132
const { preferences } = app;
130-
const { trackUsageStatistics, telemetryAnonymousId } =
133+
const { trackUsageStatistics, telemetryAnonymousId, telemetryAtlasUserId } =
131134
preferences.getPreferences();
132135
this.telemetryAnonymousId = telemetryAnonymousId ?? '';
136+
this.telemetryAtlasUserId = telemetryAtlasUserId;
133137

134138
try {
135139
this.osInfo = await getOsInfo();
@@ -176,11 +180,22 @@ class CompassTelemetry {
176180
this.state = 'disabled';
177181
}
178182
};
183+
const onAtlasUserIdChanged = (value?: string) => {
184+
if (value) {
185+
this.telemetryAtlasUserId = value;
186+
this.identify();
187+
}
188+
};
189+
179190
onTrackUsageStatisticsChanged(trackUsageStatistics); // initial setup with current value
180191
preferences.onPreferenceValueChanged(
181192
'trackUsageStatistics',
182193
onTrackUsageStatisticsChanged
183194
);
195+
preferences.onPreferenceValueChanged(
196+
'telemetryAtlasUserId',
197+
onAtlasUserIdChanged
198+
);
184199

185200
process.on('compass:track', (meta: EventInfo) => {
186201
this._track(meta);

0 commit comments

Comments
 (0)