From b350bd11a769199857d886f61048e8790f5e5bbe Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Tue, 3 Sep 2024 12:53:48 +0200 Subject: [PATCH] fix: onboarding events corner cases (#8057) --- src/lib/db/user-store.ts | 1 + .../onboarding/onboarding-service.e2e.test.ts | 51 +++++++++++++++++++ .../features/onboarding/onboarding-service.ts | 9 +++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/lib/db/user-store.ts b/src/lib/db/user-store.ts index 2be081ca3b..e6a783c564 100644 --- a/src/lib/db/user-store.ts +++ b/src/lib/db/user-store.ts @@ -302,6 +302,7 @@ class UserStore implements IUserStore { async getFirstUserDate(): Promise { const firstInstanceUser = await this.db('users') .select('created_at') + .where('is_system', '=', false) .orderBy('created_at', 'asc') .first(); diff --git a/src/lib/features/onboarding/onboarding-service.e2e.test.ts b/src/lib/features/onboarding/onboarding-service.e2e.test.ts index da438facb8..5e19b747ba 100644 --- a/src/lib/features/onboarding/onboarding-service.e2e.test.ts +++ b/src/lib/features/onboarding/onboarding-service.e2e.test.ts @@ -35,9 +35,60 @@ beforeEach(async () => { await stores.featureToggleStore.deleteAll(); await stores.projectStore.deleteAll(); await stores.onboardingStore.deleteAll(); + await stores.userStore.deleteAll(); jest.useRealTimers(); }); +test('Default project should take first user created instead of project created as start time', async () => { + jest.useFakeTimers(); + jest.setSystemTime(new Date()); + const { userStore, featureToggleStore, projectStore, projectReadModel } = + stores; + + // default projects are created in advance and should be ignored + await projectStore.create({ id: 'default', name: 'irrelevant' }); + + jest.advanceTimersByTime(minutesToMilliseconds(1)); + const user = await userStore.insert({}); + await featureToggleStore.create('default', { + name: 'test-default', + createdByUserId: user.id, + }); + + jest.advanceTimersByTime(minutesToMilliseconds(1)); + await onboardingService.insert({ + type: 'flag-created', + flag: 'test-default', + }); + await onboardingService.insert({ type: 'pre-live', flag: 'test-default' }); + await onboardingService.insert({ type: 'live', flag: 'test-default' }); + + const { rows: projectEvents } = await db.rawDatabase.raw( + 'SELECT * FROM onboarding_events_project', + ); + expect(projectEvents).toMatchObject([ + { event: 'first-flag', time_to_event: 60, project: 'default' }, + { + event: 'first-pre-live', + time_to_event: 60, + project: 'default', + }, + { event: 'first-live', time_to_event: 60, project: 'default' }, + ]); +}); + +test('Ignore system user in onboarding events', async () => { + // system users are not counted towards onboarding metrics + await db.rawDatabase.raw('INSERT INTO users (is_system) VALUES (true)'); + + await onboardingService.insert({ type: 'first-user-login' }); + + const { rows: instanceEvents } = await db.rawDatabase.raw( + 'SELECT * FROM onboarding_events_instance', + ); + expect(instanceEvents).toMatchObject([]); +}); + test('Storing onboarding events', async () => { jest.useFakeTimers(); jest.setSystemTime(new Date()); diff --git a/src/lib/features/onboarding/onboarding-service.ts b/src/lib/features/onboarding/onboarding-service.ts index ea3514f6ed..2ac13322aa 100644 --- a/src/lib/features/onboarding/onboarding-service.ts +++ b/src/lib/features/onboarding/onboarding-service.ts @@ -124,8 +124,15 @@ export class OnboardingService { ); if (!project) return; + const startDate = + project.project === 'default' + ? await this.userStore.getFirstUserDate() + : project.createdAt || null; + + if (!startDate) return; + const timeToEvent = millisecondsToSeconds( - new Date().getTime() - project.createdAt.getTime(), + Date.now() - startDate.getTime(), ); await this.onboardingStore.insertProjectEvent({ type: event.type,