diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 5895a02677..ed07befc77 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -135,6 +135,7 @@ exports[`should create default config 1`] = ` "migrationLock": true, "navigationSidebar": true, "newEventSearch": false, + "onboardingMetrics": false, "originMiddleware": false, "outdatedSdksBanner": false, "personalAccessTokensKillSwitch": false, diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 7fc5776373..5d6776cb43 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -88,7 +88,7 @@ export const createStores = ( config.flagResolver, ), settingStore: new SettingStore(db, getLogger), - userStore: new UserStore(db, getLogger), + userStore: new UserStore(db, getLogger, config.flagResolver), accountStore: new AccountStore(db, getLogger), projectStore: new ProjectStore( db, diff --git a/src/lib/db/user-store.ts b/src/lib/db/user-store.ts index 80272b7aec..a3e58d5815 100644 --- a/src/lib/db/user-store.ts +++ b/src/lib/db/user-store.ts @@ -11,6 +11,7 @@ import type { IUserUpdateFields, } from '../types/stores/user-store'; import type { Db } from './db'; +import type { IFlagResolver } from '../types'; const TABLE = 'users'; const PASSWORD_HASH_TABLE = 'used_passwords'; @@ -26,8 +27,6 @@ const USER_COLUMNS_PUBLIC = [ 'scim_id', ]; -const USED_PASSWORDS = ['user_id', 'password_hash', 'used_at']; - const USER_COLUMNS = [...USER_COLUMNS_PUBLIC, 'login_attempts', 'created_at']; const emptify = (value) => { @@ -69,9 +68,12 @@ class UserStore implements IUserStore { private logger: Logger; - constructor(db: Db, getLogger: LogProvider) { + private flagResolver: IFlagResolver; + + constructor(db: Db, getLogger: LogProvider, flagResolver: IFlagResolver) { this.db = db; this.logger = getLogger('user-store.ts'); + this.flagResolver = flagResolver; } async getPasswordsPreviouslyUsed(userId: number): Promise { @@ -230,10 +232,19 @@ class UserStore implements IUserStore { } async successfullyLogin(user: User): Promise { - return this.buildSelectUser(user).update({ + const currentDate = new Date(); + const updateQuery = this.buildSelectUser(user).update({ login_attempts: 0, - seen_at: new Date(), + seen_at: currentDate, }); + if (this.flagResolver.isEnabled('onboardingMetrics')) { + updateQuery.update({ + first_seen_at: this.db.raw('COALESCE(first_seen_at, ?)', [ + currentDate, + ]), + }); + } + return updateQuery; } async deleteAll(): Promise { diff --git a/src/lib/features/instance-stats/createInstanceStatsService.ts b/src/lib/features/instance-stats/createInstanceStatsService.ts index b406120b7b..134e9f4501 100644 --- a/src/lib/features/instance-stats/createInstanceStatsService.ts +++ b/src/lib/features/instance-stats/createInstanceStatsService.ts @@ -51,7 +51,7 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => { getLogger, flagResolver, ); - const userStore = new UserStore(db, getLogger); + const userStore = new UserStore(db, getLogger, flagResolver); const projectStore = new ProjectStore( db, eventBus, diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 54d9f6215c..9c021801a0 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -63,7 +63,8 @@ export type IFlagKey = | 'archiveProjects' | 'projectListImprovements' | 'useProjectReadModel' - | 'addonUsageMetrics'; + | 'addonUsageMetrics' + | 'onboardingMetrics'; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; @@ -308,6 +309,10 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS, false, ), + onboardingMetrics: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_ONBOARDING_METRICS, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = { diff --git a/src/server-dev.ts b/src/server-dev.ts index e5e14031f2..7f4f0c1ffa 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -56,6 +56,7 @@ process.nextTick(async () => { projectListImprovements: true, useProjectReadModel: true, addonUsageMetrics: true, + onboardingMetrics: true, }, }, authentication: {