feat(auth): add measuring registration to subscription time statistics

This commit is contained in:
Karol Sójko 2022-09-07 14:34:38 +02:00
parent 8157f324a0
commit b61825235e
No known key found for this signature in database
GPG key ID: A50543BF560BDEB0
6 changed files with 68 additions and 2 deletions

View file

@ -2,5 +2,6 @@ export enum StatisticsMeasure {
Income = 'income', Income = 'income',
SubscriptionLength = 'subscription-length', SubscriptionLength = 'subscription-length',
RegistrationLength = 'registration-length', RegistrationLength = 'registration-length',
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
Refunds = 'refunds', Refunds = 'refunds',
} }

View file

@ -16,9 +16,10 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription' import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface' import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType' import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics' import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity' import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId' import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { TimerInterface } from '@standardnotes/time'
describe('SubscriptionPurchasedEventHandler', () => { describe('SubscriptionPurchasedEventHandler', () => {
let userRepository: UserRepositoryInterface let userRepository: UserRepositoryInterface
@ -35,6 +36,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
let getUserAnalyticsId: GetUserAnalyticsId let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface let analyticsStore: AnalyticsStoreInterface
let timestamp: number let timestamp: number
let statisticsStore: StatisticsStoreInterface
let timer: TimerInterface
const createHandler = () => const createHandler = () =>
new SubscriptionPurchasedEventHandler( new SubscriptionPurchasedEventHandler(
@ -45,6 +48,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
subscriptionSettingService, subscriptionSettingService,
getUserAnalyticsId, getUserAnalyticsId,
analyticsStore, analyticsStore,
statisticsStore,
timer,
logger, logger,
) )
@ -66,7 +71,14 @@ describe('SubscriptionPurchasedEventHandler', () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue(user) userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
userRepository.save = jest.fn().mockReturnValue(user) userRepository.save = jest.fn().mockReturnValue(user)
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
statisticsStore.incrementMeasure = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(1)
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface> userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(0)
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription) userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription> offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
@ -146,6 +158,15 @@ describe('SubscriptionPurchasedEventHandler', () => {
updatedAt: expect.any(Number), updatedAt: expect.any(Number),
cancelled: false, cancelled: false,
}) })
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
})
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(1)
await createHandler().handle(event)
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
}) })
it('should update analytics on limited discount offer purchasing', async () => { it('should update analytics on limited discount offer purchasing', async () => {

View file

@ -13,8 +13,15 @@ import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface' import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType' import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface' import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics' import {
AnalyticsActivity,
AnalyticsStoreInterface,
Period,
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId' import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { TimerInterface } from '@standardnotes/time'
@injectable() @injectable()
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface { export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
@ -27,6 +34,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface, @inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId, @inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface, @inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.Logger) private logger: Logger, @inject(TYPES.Logger) private logger: Logger,
) {} ) {}
@ -52,6 +61,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
return return
} }
const previousSubscriptionCount = await this.userSubscriptionRepository.countByUserUuid(user.uuid)
const userSubscription = await this.createSubscription( const userSubscription = await this.createSubscription(
event.payload.subscriptionId, event.payload.subscriptionId,
event.payload.subscriptionName, event.payload.subscriptionName,
@ -80,6 +91,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
Period.Today, Period.Today,
]) ])
} }
if (previousSubscriptionCount === 0) {
await this.statisticsStore.incrementMeasure(
StatisticsMeasure.RegistrationToSubscriptionTime,
event.payload.timestamp - this.timer.convertDateToMicroseconds(user.createdAt),
[Period.Today, Period.ThisWeek, Period.ThisMonth],
)
}
} }
private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> { private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> {

View file

@ -4,6 +4,7 @@ import { UserSubscriptionType } from './UserSubscriptionType'
export interface UserSubscriptionRepositoryInterface { export interface UserSubscriptionRepositoryInterface {
findOneByUuid(uuid: Uuid): Promise<UserSubscription | null> findOneByUuid(uuid: Uuid): Promise<UserSubscription | null>
countByUserUuid(userUuid: Uuid): Promise<number>
findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null> findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null>
findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null> findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null>
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]> findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>

View file

@ -75,6 +75,21 @@ describe('MySQLUserSubscriptionRepository', () => {
expect(result).toEqual(subscription) expect(result).toEqual(subscription)
}) })
it('should count by user uuid', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getCount = jest.fn().mockReturnValue(2)
const result = await createRepository().countByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.getCount).toHaveBeenCalled()
expect(result).toEqual(2)
})
it('should find one, longest lasting subscription by user uuid if there are no ucanceled ones', async () => { it('should find one, longest lasting subscription by user uuid if there are no ucanceled ones', async () => {
subscription.cancelled = true subscription.cancelled = true

View file

@ -14,6 +14,15 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
private ormRepository: Repository<UserSubscription>, private ormRepository: Repository<UserSubscription>,
) {} ) {}
async countByUserUuid(userUuid: Uuid): Promise<number> {
return await this.ormRepository
.createQueryBuilder()
.where('user_uuid = :user_uuid', {
user_uuid: userUuid,
})
.getCount()
}
async save(subscription: UserSubscription): Promise<UserSubscription> { async save(subscription: UserSubscription): Promise<UserSubscription> {
return this.ormRepository.save(subscription) return this.ormRepository.save(subscription)
} }