feat(analytics): add statistics measurements tracking

This commit is contained in:
Karol Sójko 2022-09-05 16:39:38 +02:00
parent 9e2aea2793
commit a36cb925ff
No known key found for this signature in database
GPG key ID: A50543BF560BDEB0
5 changed files with 76 additions and 1 deletions

View file

@ -0,0 +1,3 @@
export enum StatisticsMeasure {
PaymentSuccess = 'payment-success',
}

View file

@ -1,3 +1,6 @@
import { Period } from '../Time/Period'
import { StatisticsMeasure } from './StatisticsMeasure'
export interface StatisticsStoreInterface {
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
incrementApplicationVersionUsage(applicationVersion: string): Promise<void>
@ -5,4 +8,7 @@ export interface StatisticsStoreInterface {
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: number }>>
getYesterdayOutOfSyncIncidents(): Promise<number>
incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void>
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
getMeasureTotal(measure: StatisticsMeasure, period: Period): Promise<number>
}

View file

@ -1,5 +1,6 @@
export * from './Analytics/AnalyticsActivity'
export * from './Analytics/AnalyticsStoreInterface'
export * from './Statistics/StatisticsMeasure'
export * from './Statistics/StatisticsStoreInterface'
export * from './Time/Period'
export * from './Time/PeriodKeyGenerator'

View file

@ -1,5 +1,6 @@
import * as IORedis from 'ioredis'
import { PeriodKeyGeneratorInterface } from '../../Domain'
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
import { RedisStatisticsStore } from './RedisStatisticsStore'
@ -13,6 +14,7 @@ describe('RedisStatisticsStore', () => {
beforeEach(() => {
pipeline = {} as jest.Mocked<IORedis.Pipeline>
pipeline.incr = jest.fn()
pipeline.incrby = jest.fn()
pipeline.setbit = jest.fn()
pipeline.exec = jest.fn()
@ -88,4 +90,30 @@ describe('RedisStatisticsStore', () => {
expect(pipeline.incr).toHaveBeenCalled()
expect(pipeline.exec).toHaveBeenCalled()
})
it('should increment measure by a value', async () => {
await createStore().incrementMeasure(StatisticsMeasure.PaymentSuccess, 2, [Period.Today, Period.ThisMonth])
expect(pipeline.incr).toHaveBeenCalledTimes(2)
expect(pipeline.incrby).toHaveBeenCalledTimes(2)
expect(pipeline.exec).toHaveBeenCalled()
})
it('should count a measurement average', async () => {
redisClient.get = jest.fn().mockReturnValueOnce('5').mockReturnValueOnce('2')
expect(await createStore().getMeasureAverage(StatisticsMeasure.PaymentSuccess, Period.Today)).toEqual(2 / 5)
})
it('should count a measurement average - 0 increments', async () => {
redisClient.get = jest.fn().mockReturnValueOnce(null).mockReturnValueOnce(null)
expect(await createStore().getMeasureAverage(StatisticsMeasure.PaymentSuccess, Period.Today)).toEqual(0)
})
it('should count a measurement average - 0 total value', async () => {
redisClient.get = jest.fn().mockReturnValueOnce(5).mockReturnValueOnce(null)
expect(await createStore().getMeasureAverage(StatisticsMeasure.PaymentSuccess, Period.Today)).toEqual(0)
})
})

View file

@ -1,12 +1,49 @@
import * as IORedis from 'ioredis'
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
export class RedisStatisticsStore implements StatisticsStoreInterface {
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
async getMeasureTotal(measure: StatisticsMeasure, period: Period): Promise<number> {
const totalValue = await this.redisClient.get(
`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
)
if (totalValue === null) {
return 0
}
return +totalValue
}
async incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void> {
const pipeline = this.redisClient.pipeline()
for (const period of periods) {
pipeline.incrby(`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`, value)
pipeline.incr(`count:increments:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`)
}
await pipeline.exec()
}
async getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number> {
const increments = await this.redisClient.get(
`count:increments:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
)
if (increments === null) {
return 0
}
const totalValue = await this.getMeasureTotal(measure, period)
return totalValue / +increments
}
async getYesterdayOutOfSyncIncidents(): Promise<number> {
const count = await this.redisClient.get(
`count:action:out-of-sync:timespan:${this.periodKeyGenerator.getPeriodKey(Period.Yesterday)}`,