feat(analytics): add statistics measurements tracking
This commit is contained in:
parent
9e2aea2793
commit
a36cb925ff
5 changed files with 76 additions and 1 deletions
|
@ -0,0 +1,3 @@
|
||||||
|
export enum StatisticsMeasure {
|
||||||
|
PaymentSuccess = 'payment-success',
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
import { StatisticsMeasure } from './StatisticsMeasure'
|
||||||
|
|
||||||
export interface StatisticsStoreInterface {
|
export interface StatisticsStoreInterface {
|
||||||
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
|
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
|
||||||
incrementApplicationVersionUsage(applicationVersion: string): Promise<void>
|
incrementApplicationVersionUsage(applicationVersion: string): Promise<void>
|
||||||
|
@ -5,4 +8,7 @@ export interface StatisticsStoreInterface {
|
||||||
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
|
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
|
||||||
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: number }>>
|
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: number }>>
|
||||||
getYesterdayOutOfSyncIncidents(): Promise<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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './Analytics/AnalyticsActivity'
|
export * from './Analytics/AnalyticsActivity'
|
||||||
export * from './Analytics/AnalyticsStoreInterface'
|
export * from './Analytics/AnalyticsStoreInterface'
|
||||||
|
export * from './Statistics/StatisticsMeasure'
|
||||||
export * from './Statistics/StatisticsStoreInterface'
|
export * from './Statistics/StatisticsStoreInterface'
|
||||||
export * from './Time/Period'
|
export * from './Time/Period'
|
||||||
export * from './Time/PeriodKeyGenerator'
|
export * from './Time/PeriodKeyGenerator'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as IORedis from 'ioredis'
|
import * as IORedis from 'ioredis'
|
||||||
import { PeriodKeyGeneratorInterface } from '../../Domain'
|
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
||||||
|
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||||
|
|
||||||
import { RedisStatisticsStore } from './RedisStatisticsStore'
|
import { RedisStatisticsStore } from './RedisStatisticsStore'
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ describe('RedisStatisticsStore', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pipeline = {} as jest.Mocked<IORedis.Pipeline>
|
pipeline = {} as jest.Mocked<IORedis.Pipeline>
|
||||||
pipeline.incr = jest.fn()
|
pipeline.incr = jest.fn()
|
||||||
|
pipeline.incrby = jest.fn()
|
||||||
pipeline.setbit = jest.fn()
|
pipeline.setbit = jest.fn()
|
||||||
pipeline.exec = jest.fn()
|
pipeline.exec = jest.fn()
|
||||||
|
|
||||||
|
@ -88,4 +90,30 @@ describe('RedisStatisticsStore', () => {
|
||||||
expect(pipeline.incr).toHaveBeenCalled()
|
expect(pipeline.incr).toHaveBeenCalled()
|
||||||
expect(pipeline.exec).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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,49 @@
|
||||||
import * as IORedis from 'ioredis'
|
import * as IORedis from 'ioredis'
|
||||||
|
|
||||||
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
||||||
|
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||||
|
|
||||||
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
|
||||||
|
|
||||||
export class RedisStatisticsStore implements StatisticsStoreInterface {
|
export class RedisStatisticsStore implements StatisticsStoreInterface {
|
||||||
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
|
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> {
|
async getYesterdayOutOfSyncIncidents(): Promise<number> {
|
||||||
const count = await this.redisClient.get(
|
const count = await this.redisClient.get(
|
||||||
`count:action:out-of-sync:timespan:${this.periodKeyGenerator.getPeriodKey(Period.Yesterday)}`,
|
`count:action:out-of-sync:timespan:${this.periodKeyGenerator.getPeriodKey(Period.Yesterday)}`,
|
||||||
|
|
Loading…
Reference in a new issue