Skip to content

Commit 1423539

Browse files
authored
fix(compass): make sure we get active user info when setting up intercom (#5418)
* fix(compass): make sure we get active user info when setting up intercom * test(e2e): add a test to check that intercom is loaded * chore: fix log id
1 parent a6d42f2 commit 1423539

File tree

12 files changed

+120
-42
lines changed

12 files changed

+120
-42
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('AtlasServiceMain', function () {
8484
AtlasService['createMongoDBOIDCPlugin'] = () => mockOidcPlugin;
8585
AtlasService['atlasUserConfigStore'] =
8686
mockUserConfigStore as unknown as AtlasUserConfigStore;
87-
AtlasService['getUserId'] = () => Promise.resolve('test');
87+
AtlasService['getUserId'] = () => 'test';
8888

8989
AtlasService['config'] = defaultConfig;
9090

packages/atlas-service/src/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export class AtlasService {
168168
| Pick<HadronIpcMain, 'createHandle' | 'handle' | 'broadcast'>
169169
| undefined = ipcMain;
170170

171-
private static getUserId: () => Promise<string>;
171+
private static getUserId: () => string;
172172
private static preferences: PreferencesAccess;
173173
private static config: AtlasServiceConfig;
174174

@@ -237,7 +237,7 @@ export class AtlasService {
237237
{
238238
preferences,
239239
getUserId,
240-
}: { preferences: PreferencesAccess; getUserId: () => Promise<string> }
240+
}: { preferences: PreferencesAccess; getUserId: () => string }
241241
): Promise<void> {
242242
this.preferences = preferences;
243243
this.getUserId = getUserId;
@@ -574,7 +574,7 @@ export class AtlasService {
574574
static async getAIFeatureEnablement(): Promise<AIFeatureEnablement> {
575575
this.throwIfNetworkTrafficDisabled();
576576

577-
const userId = await this.getUserId();
577+
const userId = this.getUserId();
578578
const url = `${this.config.atlasApiUnauthBaseUrl}/ai/api/v1/hello/${userId}`;
579579

580580
log.info(
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { init, cleanup, screenshotIfFailed } from '../helpers/compass';
2+
import type { Compass } from '../helpers/compass';
3+
4+
describe('Intercom integration', function () {
5+
let compass: Compass;
6+
7+
before(async function () {
8+
compass = await init(this.test?.fullTitle(), { firstRun: true });
9+
});
10+
11+
afterEach(async function () {
12+
await screenshotIfFailed(compass, this.currentTest);
13+
});
14+
15+
after(async function () {
16+
// clean up if it failed during the before hook
17+
await cleanup(compass);
18+
});
19+
20+
it('should load Intercom script', async function () {
21+
const isIntercomAvailable = await compass.browser.execute(() => {
22+
return !!process.env.HADRON_METRICS_INTERCOM_APP_ID;
23+
});
24+
25+
// Skip the test if conditions for setting up intercom are not met (they
26+
// should always be in CI, this is to make sure this test doesn't fail
27+
// locally)
28+
if (!isIntercomAvailable && !(process.env.ci || process.env.CI)) {
29+
this.skip();
30+
}
31+
32+
await compass.browser.waitUntil(
33+
() => {
34+
return compass.browser.execute(() => {
35+
return typeof (window as any).Intercom === 'function';
36+
});
37+
},
38+
{
39+
timeoutMsg:
40+
'Expected Intercom SDK to load and be available in the global scope',
41+
}
42+
);
43+
});
44+
});

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ export async function setupPreferencesAndUser(
1111
const preferences = await setupPreferences(globalPreferences);
1212
const userStorage = new UserStorageImpl();
1313
const user = await userStorage.getOrCreate(getActiveUserId(preferences));
14-
// update user id (telemetryAnonymousId) in preferences if new user was created.
15-
await preferences.savePreferences({ telemetryAnonymousId: user.id });
14+
// update user info (telemetryAnonymousId and userCreatedAt) in preferences to
15+
// make sure user info is in sync between preferences and UserStorage and can
16+
// be accessed in renderer without the need to use UserStorage directly (we
17+
// can't access the same UserStorage instance in renderer)
18+
await preferences.savePreferences({
19+
telemetryAnonymousId: user.id,
20+
userCreatedAt: user.createdAt.getTime(),
21+
});
1622
await userStorage.updateUser(user.id, {
1723
lastUsed: new Date(),
1824
});

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

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

7273
// UserPreferences contains all preferences stored to disk.
@@ -311,6 +312,17 @@ export const storedUserPreferencesProps: Required<{
311312
validator: z.string().uuid().optional(),
312313
type: 'string',
313314
},
315+
/**
316+
* Stores the timestamp for when the user was created
317+
*/
318+
userCreatedAt: {
319+
ui: false,
320+
cli: false,
321+
global: false,
322+
description: null,
323+
validator: z.number().default(Date.now()),
324+
type: 'number',
325+
},
314326
/**
315327
* Enable/disable the AI services. This is currently set
316328
* in the atlas-service initialization where we make a request to the

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { usePreference } from './react';
22
import type { AllPreferences, PreferencesAccess, User } from '.';
3-
import type { UserStorage } from './user-storage';
43

54
export function getActiveUserId(
65
preferences: PreferencesAccess
@@ -9,15 +8,15 @@ export function getActiveUserId(
98
return currentUserId || telemetryAnonymousId;
109
}
1110

12-
export async function getActiveUser(
13-
preferences: PreferencesAccess,
14-
userStorage: UserStorage
15-
): Promise<User> {
11+
export function getActiveUser(
12+
preferences: PreferencesAccess
13+
): Pick<User, 'id' | 'createdAt'> {
1614
const userId = getActiveUserId(preferences);
15+
const { userCreatedAt } = preferences.getPreferences();
1716
if (!userId) {
1817
throw new Error('User not setup.');
1918
}
20-
return userStorage.getUser(userId);
19+
return { id: userId, createdAt: new Date(userCreatedAt) };
2120
}
2221

2322
/**
Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import dns from 'dns';
55
import { ipcRenderer } from 'hadron-ipc';
66
import * as remote from '@electron/remote';
77
import { AppRegistryProvider, globalAppRegistry } from 'hadron-app-registry';
8-
import {
9-
defaultPreferencesInstance,
10-
getActiveUser,
11-
} from 'compass-preferences-model';
8+
import { defaultPreferencesInstance } from 'compass-preferences-model';
129
import { CompassHomePlugin } from '@mongodb-js/compass-home';
1310
import { PreferencesProvider } from 'compass-preferences-model/provider';
1411

@@ -24,7 +21,7 @@ if (!process.env.NODE_OPTIONS.includes('--dns-result-order')) {
2421
// Setup error reporting to main process before anything else.
2522
window.addEventListener('error', (event) => {
2623
event.preventDefault();
27-
ipcRenderer?.call(
24+
void ipcRenderer?.call(
2825
'compass:error:fatal',
2926
event.error
3027
? { message: event.error.message, stack: event.error.stack }
@@ -65,7 +62,7 @@ import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
6562
const { log, mongoLogId, track } = createLoggerAndTelemetry('COMPASS-APP');
6663

6764
// Lets us call `setShowDevFeatureFlags(true | false)` from DevTools.
68-
window.setShowDevFeatureFlags = async (showDevFeatureFlags = true) => {
65+
(window as any).setShowDevFeatureFlags = async (showDevFeatureFlags = true) => {
6966
await defaultPreferencesInstance.savePreferences({ showDevFeatureFlags });
7067
};
7168

@@ -108,14 +105,20 @@ const Application = View.extend({
108105
* @see NODE-4281
109106
* @todo: remove when NODE-4281 is merged.
110107
*/
111-
Number.prototype.unref = () => {};
108+
(Number.prototype as any).unref = () => {
109+
// noop
110+
};
112111

113-
function trackPerfEvent({ name, value }) {
112+
function trackPerfEvent({
113+
name,
114+
value,
115+
}: Pick<webvitals.Metric, 'name' | 'value'>) {
114116
const fullName = {
115117
FCP: 'First Contentful Paint',
116118
LCP: 'Largest Contentful Paint',
117119
FID: 'First Input Delay',
118120
CLS: 'Cumulative Layout Shift',
121+
TTFB: 'Time to First Byte',
119122
}[name];
120123
track(fullName, { value });
121124
}
@@ -211,9 +214,16 @@ const app = {
211214
state.preRender();
212215

213216
try {
214-
const user = await getActiveUser(defaultPreferencesInstance);
215-
setupIntercom(user, defaultPreferencesInstance);
217+
void setupIntercom(defaultPreferencesInstance);
216218
} catch (e) {
219+
log.warn(
220+
mongoLogId(1_001_000_289),
221+
'Main Window',
222+
'Failed to set up Intercom',
223+
{
224+
error: (e as Error).message,
225+
}
226+
);
217227
// noop
218228
}
219229
// Catch a data refresh coming from window-manager.
@@ -273,4 +283,4 @@ Object.defineProperty(app, 'state', {
273283
},
274284
});
275285

276-
app.init();
286+
void app.init();

packages/compass/src/app/intercom/setup-intercom.spec.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ describe('setupIntercom', function () {
2222
let fetchMock: SinonStub;
2323
let preferences: PreferencesAccess;
2424

25-
async function testRunSetupIntercom(user: User) {
25+
async function testRunSetupIntercom() {
2626
const intercomScript = {
2727
load: sinon.spy(),
2828
unload: sinon.spy(),
2929
};
3030
await setupIntercom(
31-
user,
3231
preferences,
3332
intercomScript as unknown as IntercomScript
3433
);
@@ -55,6 +54,8 @@ describe('setupIntercom', function () {
5554
preferences = await createSandboxFromDefaultPreferences();
5655
await preferences.savePreferences({
5756
enableFeedbackPanel: true,
57+
telemetryAnonymousId: mockUser.id,
58+
userCreatedAt: mockUser.createdAt.getTime(),
5859
});
5960
});
6061

@@ -72,7 +73,7 @@ describe('setupIntercom', function () {
7273
await preferences.savePreferences({
7374
enableFeedbackPanel: true,
7475
});
75-
const { intercomScript } = await testRunSetupIntercom(mockUser);
76+
const { intercomScript } = await testRunSetupIntercom();
7677

7778
expect(intercomScript.load).to.have.been.calledWith({
7879
app_id: 'appid123',
@@ -94,15 +95,15 @@ describe('setupIntercom', function () {
9495
await preferences.savePreferences({
9596
enableFeedbackPanel: false,
9697
});
97-
const { intercomScript } = await testRunSetupIntercom(mockUser);
98+
const { intercomScript } = await testRunSetupIntercom();
9899
expect(intercomScript.load).not.to.have.been.called;
99100
expect(intercomScript.unload).to.have.been.called;
100101
});
101102
});
102103

103104
describe('when cannot be enabled', function () {
104105
async function expectSetupGetsSkipped() {
105-
const { intercomScript } = await testRunSetupIntercom(mockUser);
106+
const { intercomScript } = await testRunSetupIntercom();
106107

107108
expect(intercomScript.load).to.not.have.been.called;
108109

packages/compass/src/app/intercom/setup-intercom.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
22
import type { IntercomMetadata } from './intercom-script';
33
import { IntercomScript, buildIntercomScriptUrl } from './intercom-script';
4-
5-
import type { PreferencesAccess, User } from 'compass-preferences-model';
4+
import type { PreferencesAccess } from 'compass-preferences-model';
5+
import { getActiveUser } from 'compass-preferences-model';
66

77
const { debug } = createLoggerAndTelemetry('COMPASS-INTERCOM');
88

99
export async function setupIntercom(
10-
user: User,
1110
preferences: PreferencesAccess,
1211
intercomScript: IntercomScript = new IntercomScript()
1312
): Promise<void> {
@@ -26,6 +25,8 @@ export async function setupIntercom(
2625
return;
2726
}
2827

28+
const user = getActiveUser(preferences);
29+
2930
const metadata: IntercomMetadata = {
3031
user_id: user.id,
3132
app_id: intercomAppId,

packages/compass/src/index.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,16 @@ declare module 'process' {
4141
}
4242
}
4343
}
44+
45+
declare module 'marky' {
46+
declare function mark(label: string): void;
47+
declare function stop(label: string): void;
48+
export { mark, stop };
49+
}
50+
51+
declare module 'ampersand-view' {
52+
class View {
53+
static extend(...args: any): any;
54+
}
55+
export default View;
56+
}

0 commit comments

Comments
 (0)