Compare commits

..

15 Commits

Author SHA1 Message Date
standardci
8c6cf9651d chore(release): publish new version
- @standardnotes/api-gateway@1.24.5
 - @standardnotes/auth-server@1.37.0
 - @standardnotes/common@1.35.0
 - @standardnotes/domain-events-infra@1.8.16
 - @standardnotes/domain-events@2.62.0
 - @standardnotes/event-store@1.3.21
 - @standardnotes/files-server@1.6.7
 - @standardnotes/predicates@1.4.4
 - @standardnotes/scheduler-server@1.10.35
 - @standardnotes/security@1.4.2
 - @standardnotes/syncing-server@1.8.19
2022-10-04 13:17:02 +00:00
Karol Sójko
8668fec33d feat(auth): add detailed income stats 2022-10-04 15:15:32 +02:00
standardci
76e34131fb chore(release): publish new version
- @standardnotes/api-gateway@1.24.4
 - @standardnotes/auth-server@1.36.4
 - @standardnotes/common@1.34.0
 - @standardnotes/domain-events-infra@1.8.15
 - @standardnotes/domain-events@2.61.1
 - @standardnotes/event-store@1.3.20
 - @standardnotes/files-server@1.6.6
 - @standardnotes/predicates@1.4.3
 - @standardnotes/scheduler-server@1.10.34
 - @standardnotes/security@1.4.1
 - @standardnotes/syncing-server@1.8.18
2022-10-04 12:17:15 +00:00
Karol Sójko
3c40ee4b4a feat(common): add subscription billing frequency 2022-10-04 14:15:45 +02:00
standardci
5abd7ae32c chore(release): publish new version
- @standardnotes/analytics@1.34.0
 - @standardnotes/api-gateway@1.24.3
 - @standardnotes/auth-server@1.36.3
 - @standardnotes/syncing-server@1.8.17
2022-10-04 11:26:29 +00:00
Karol Sójko
09b3f9a0d7 fix(auth): turn down severity of logs for predicate verification 2022-10-04 13:24:58 +02:00
Karol Sójko
19455ba6a7 feat(analytics): add new statistics measures for income 2022-10-04 13:24:58 +02:00
standardci
7d042689f0 chore(release): publish new version
- @standardnotes/api-gateway@1.24.2
2022-10-03 12:49:37 +00:00
Karol Sójko
f43fbf1584 fix(api-gateway): report churn values for empty months 2022-10-03 14:47:45 +02:00
standardci
24c0cb8366 chore(release): publish new version
- @standardnotes/api-gateway@1.24.1
2022-10-03 12:15:56 +00:00
Karol Sójko
2236cc3828 fix: add debug logs for churn calculation 2022-10-03 14:14:27 +02:00
standardci
039d44718a chore(release): publish new version
- @standardnotes/analytics@1.33.0
 - @standardnotes/api-gateway@1.24.0
 - @standardnotes/auth-server@1.36.2
 - @standardnotes/domain-events-infra@1.8.14
 - @standardnotes/domain-events@2.61.0
 - @standardnotes/event-store@1.3.19
 - @standardnotes/files-server@1.6.5
 - @standardnotes/scheduler-server@1.10.33
 - @standardnotes/syncing-server@1.8.16
2022-10-03 11:22:13 +00:00
Karol Sójko
f075cd8c4d feat: add calculating monthly churn rate 2022-10-03 13:19:53 +02:00
standardci
ea0f3e8999 chore(release): publish new version
- @standardnotes/auth-server@1.36.1
2022-10-03 08:40:15 +00:00
Karol Sójko
e7736bba25 fix(auth): counting active subscriptions 2022-10-03 10:38:31 +02:00
47 changed files with 615 additions and 39 deletions

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.34.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.33.0...@standardnotes/analytics@1.34.0) (2022-10-04)
### Features
* **analytics:** add new statistics measures for income ([19455ba](https://github.com/standardnotes/server/commit/19455ba6a7d84a389830c728c3dfea550b156985))
# [1.33.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.32.0...@standardnotes/analytics@1.33.0) (2022-10-03)
### Features
* add calculating monthly churn rate ([f075cd8](https://github.com/standardnotes/server/commit/f075cd8c4dfc411ba513dfec21bb84c03b238254))
# [1.32.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.31.1...@standardnotes/analytics@1.32.0) (2022-09-30)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "1.32.0",
"version": "1.34.0",
"engines": {
"node": ">=14.0.0 <17.0.0"
},

View File

@@ -12,7 +12,7 @@ export interface AnalyticsStoreInterface {
secondActivity: AnalyticsActivity
secondActivityPeriodKey: string
}): Promise<number>
calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number>
calculateActivityTotalCount(activity: AnalyticsActivity, periodOrPeriodKey: Period | string): Promise<number>
calculateActivityChangesTotalCount(
activity: AnalyticsActivity,
period: Period,

View File

@@ -1,5 +1,13 @@
export enum StatisticsMeasure {
Income = 'income',
PlusSubscriptionInitialMonthlyPaymentsIncome = 'plus-subscription-initial-monthly-payments-income',
ProSubscriptionInitialMonthlyPaymentsIncome = 'pro-subscription-initial-monthly-payments-income',
PlusSubscriptionInitialAnnualPaymentsIncome = 'plus-subscription-initial-annual-payments-income',
ProSubscriptionInitialAnnualPaymentsIncome = 'pro-subscription-initial-annual-payments-income',
PlusSubscriptionRenewingMonthlyPaymentsIncome = 'plus-subscription-renewing-monthly-payments-income',
ProSubscriptionRenewingMonthlyPaymentsIncome = 'pro-subscription-renewing-monthly-payments-income',
PlusSubscriptionRenewingAnnualPaymentsIncome = 'plus-subscription-renewing-annual-payments-income',
ProSubscriptionRenewingAnnualPaymentsIncome = 'pro-subscription-renewing-annual-payments-income',
SubscriptionLength = 'subscription-length',
RegistrationLength = 'registration-length',
RegistrationToSubscriptionTime = 'registration-to-subscription-time',

View File

@@ -11,5 +11,5 @@ export interface StatisticsStoreInterface {
incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void>
setMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void>
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
getMeasureTotal(measure: StatisticsMeasure, period: Period): Promise<number>
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number>
}

View File

@@ -14,4 +14,16 @@ export enum Period {
Q2ThisYear,
Q3ThisYear,
Q4ThisYear,
JanuaryThisYear,
FebruaryThisYear,
MarchThisYear,
AprilThisYear,
MayThisYear,
JuneThisYear,
JulyThisYear,
AugustThisYear,
SeptemberThisYear,
OctoberThisYear,
NovemberThisYear,
DecemberThisYear,
}

View File

@@ -3,6 +3,20 @@ import { PeriodKeyGenerator } from './PeriodKeyGenerator'
describe('PeriodKeyGenerator', () => {
const createGenerator = () => new PeriodKeyGenerator()
const months = [
Period.JanuaryThisYear,
Period.FebruaryThisYear,
Period.MarchThisYear,
Period.AprilThisYear,
Period.MayThisYear,
Period.JuneThisYear,
Period.JulyThisYear,
Period.AugustThisYear,
Period.SeptemberThisYear,
Period.OctoberThisYear,
Period.NovemberThisYear,
Period.DecemberThisYear,
]
beforeEach(() => {
jest.useFakeTimers()
@@ -48,6 +62,23 @@ describe('PeriodKeyGenerator', () => {
])
})
it('should generate period keys for this year', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.ThisYear)).toEqual([
'2022-1',
'2022-2',
'2022-3',
'2022-4',
'2022-5',
'2022-6',
'2022-7',
'2022-8',
'2022-9',
'2022-10',
'2022-11',
'2022-12',
])
})
it('should generate period keys for last 7 days', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.Last7Days)).toEqual([
'2022-5-17',
@@ -60,6 +91,81 @@ describe('PeriodKeyGenerator', () => {
])
})
it('should generate period keys for this month', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.ThisMonth)).toEqual([
'2022-5-1',
'2022-5-2',
'2022-5-3',
'2022-5-4',
'2022-5-5',
'2022-5-6',
'2022-5-7',
'2022-5-8',
'2022-5-9',
'2022-5-10',
'2022-5-11',
'2022-5-12',
'2022-5-13',
'2022-5-14',
'2022-5-15',
'2022-5-16',
'2022-5-17',
'2022-5-18',
'2022-5-19',
'2022-5-20',
'2022-5-21',
'2022-5-22',
'2022-5-23',
'2022-5-24',
'2022-5-25',
'2022-5-26',
'2022-5-27',
'2022-5-28',
'2022-5-29',
'2022-5-30',
'2022-5-31',
])
})
it('should generate period keys for specific month', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.FebruaryThisYear)).toEqual([
'2022-2-1',
'2022-2-2',
'2022-2-3',
'2022-2-4',
'2022-2-5',
'2022-2-6',
'2022-2-7',
'2022-2-8',
'2022-2-9',
'2022-2-10',
'2022-2-11',
'2022-2-12',
'2022-2-13',
'2022-2-14',
'2022-2-15',
'2022-2-16',
'2022-2-17',
'2022-2-18',
'2022-2-19',
'2022-2-20',
'2022-2-21',
'2022-2-22',
'2022-2-23',
'2022-2-24',
'2022-2-25',
'2022-2-26',
'2022-2-27',
'2022-2-28',
])
})
it('should generate period keys for specific months', () => {
for (const month of months) {
expect(createGenerator().getDiscretePeriodKeys(month).length >= 28).toBeTruthy()
}
})
it('should generate period keys for Q1', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
})
@@ -76,6 +182,10 @@ describe('PeriodKeyGenerator', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.Q4ThisYear)).toEqual(['2022-10', '2022-11', '2022-12'])
})
it('should generate a period key for this year', () => {
expect(createGenerator().getPeriodKey(Period.ThisYear)).toEqual('2022')
})
it('should generate a period key for today', () => {
expect(createGenerator().getPeriodKey(Period.Today)).toEqual('2022-5-24')
})
@@ -104,6 +214,12 @@ describe('PeriodKeyGenerator', () => {
expect(createGenerator().getPeriodKey(Period.ThisMonth)).toEqual('2022-5')
})
it('should generate a period key for each month', () => {
for (let i = 0; i < months.length; i++) {
expect(createGenerator().getPeriodKey(months[i])).toEqual(`2022-${i + 1}`)
}
})
it('should generate a period key for last month', () => {
expect(createGenerator().getPeriodKey(Period.LastMonth)).toEqual('2022-4')
})
@@ -129,4 +245,19 @@ describe('PeriodKeyGenerator', () => {
expect(error).not.toBeNull()
})
it('should convert period key to period', () => {
expect(createGenerator().convertPeriodKeyToPeriod('2022-1')).toEqual(Period.JanuaryThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-2')).toEqual(Period.FebruaryThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-3')).toEqual(Period.MarchThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-4')).toEqual(Period.AprilThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-5')).toEqual(Period.MayThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-6')).toEqual(Period.JuneThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-7')).toEqual(Period.JulyThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-8')).toEqual(Period.AugustThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-9')).toEqual(Period.SeptemberThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-10')).toEqual(Period.OctoberThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-11')).toEqual(Period.NovemberThisYear)
expect(createGenerator().convertPeriodKeyToPeriod('2022-12')).toEqual(Period.DecemberThisYear)
})
})

View File

@@ -2,6 +2,28 @@ import { Period } from './Period'
import { PeriodKeyGeneratorInterface } from './PeriodKeyGeneratorInterface'
export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
private readonly MONTHS = [
Period.JanuaryThisYear,
Period.FebruaryThisYear,
Period.MarchThisYear,
Period.AprilThisYear,
Period.MayThisYear,
Period.JuneThisYear,
Period.JulyThisYear,
Period.AugustThisYear,
Period.SeptemberThisYear,
Period.OctoberThisYear,
Period.NovemberThisYear,
Period.DecemberThisYear,
]
convertPeriodKeyToPeriod(periodKey: string): Period {
const date = new Date(periodKey)
const month = this.getMonth(date)
return this.MONTHS[+month - 1]
}
getDiscretePeriodKeys(period: Period): string[] {
const periodKeys = []
@@ -26,6 +48,23 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
return this.generateMonthlyKeysRange(6, 9)
case Period.Q4ThisYear:
return this.generateMonthlyKeysRange(9, 12)
case Period.ThisYear:
return this.generateMonthlyKeysRange(0, 12)
case Period.ThisMonth:
return this.generateDailyKeysRange()
case Period.JanuaryThisYear:
case Period.FebruaryThisYear:
case Period.MarchThisYear:
case Period.AprilThisYear:
case Period.MayThisYear:
case Period.JuneThisYear:
case Period.JulyThisYear:
case Period.AugustThisYear:
case Period.SeptemberThisYear:
case Period.OctoberThisYear:
case Period.NovemberThisYear:
case Period.DecemberThisYear:
return this.generateDailyKeysRange(period - 15)
default:
throw new Error(`Unsuporrted period: ${period}`)
}
@@ -51,6 +90,30 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
return this.getMonthlyKey(this.getLastMonthDate())
case Period.ThisYear:
return this.getYearlyKey()
case Period.JanuaryThisYear:
return this.generateMonthlyKeysRange(0, 1)[0]
case Period.FebruaryThisYear:
return this.generateMonthlyKeysRange(1, 2)[0]
case Period.MarchThisYear:
return this.generateMonthlyKeysRange(2, 3)[0]
case Period.AprilThisYear:
return this.generateMonthlyKeysRange(3, 4)[0]
case Period.MayThisYear:
return this.generateMonthlyKeysRange(4, 5)[0]
case Period.JuneThisYear:
return this.generateMonthlyKeysRange(5, 6)[0]
case Period.JulyThisYear:
return this.generateMonthlyKeysRange(6, 7)[0]
case Period.AugustThisYear:
return this.generateMonthlyKeysRange(7, 8)[0]
case Period.SeptemberThisYear:
return this.generateMonthlyKeysRange(8, 9)[0]
case Period.OctoberThisYear:
return this.generateMonthlyKeysRange(9, 10)[0]
case Period.NovemberThisYear:
return this.generateMonthlyKeysRange(10, 11)[0]
case Period.DecemberThisYear:
return this.generateMonthlyKeysRange(11, 12)[0]
default:
throw new Error(`Unsuporrted period: ${period}`)
}
@@ -149,4 +212,22 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
return keys
}
private generateDailyKeysRange(month?: number): string[] {
const today = new Date()
if (month) {
today.setMonth(month)
}
const numberOfDays = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate()
const keys = []
for (let i = 1; i <= numberOfDays; i++) {
const date = new Date()
date.setMonth(today.getMonth())
date.setDate(i)
keys.push(this.getDailyKey(date))
}
return keys
}
}

View File

@@ -2,5 +2,6 @@ import { Period } from './Period'
export interface PeriodKeyGeneratorInterface {
getPeriodKey(period: Period): string
convertPeriodKeyToPeriod(periodKey: string): Period
getDiscretePeriodKeys(period: Period): string[]
}

View File

@@ -102,7 +102,7 @@ describe('RedisAnalyticsStore', () => {
expect(caughtError).not.toBeNull()
})
it('should calculate total count of activities', async () => {
it('should calculate total count of activities by period', async () => {
redisClient.bitcount = jest.fn().mockReturnValue(70)
expect(await createStore().calculateActivityTotalCount(AnalyticsActivity.EditingItems, Period.Yesterday)).toEqual(
@@ -112,6 +112,14 @@ describe('RedisAnalyticsStore', () => {
expect(redisClient.bitcount).toHaveBeenCalledWith('bitmap:action:editing-items:timespan:period-key')
})
it('should calculate total count of activities by period key', async () => {
redisClient.bitcount = jest.fn().mockReturnValue(70)
expect(await createStore().calculateActivityTotalCount(AnalyticsActivity.EditingItems, '2022-10-03')).toEqual(70)
expect(redisClient.bitcount).toHaveBeenCalledWith('bitmap:action:editing-items:timespan:2022-10-03')
})
it('should calculate activity retention', async () => {
redisClient.bitcount = jest.fn().mockReturnValueOnce(7).mockReturnValueOnce(10)

View File

@@ -134,9 +134,12 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
})
}
async calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number> {
return this.redisClient.bitcount(
`bitmap:action:${activity}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
)
async calculateActivityTotalCount(activity: AnalyticsActivity, periodOrPeriodKey: Period | string): Promise<number> {
let periodKey = periodOrPeriodKey
if (!isNaN(+periodOrPeriodKey)) {
periodKey = this.periodKeyGenerator.getPeriodKey(periodOrPeriodKey as Period)
}
return this.redisClient.bitcount(`bitmap:action:${activity}:timespan:${periodKey}`)
}
}

View File

@@ -125,4 +125,20 @@ describe('RedisStatisticsStore', () => {
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(0)
})
it('should retrieve a measurement total for period', async () => {
redisClient.get = jest.fn().mockReturnValueOnce(5)
expect(await createStore().getMeasureTotal(StatisticsMeasure.Income, Period.Today)).toEqual(5)
expect(redisClient.get).toHaveBeenCalledWith('count:measure:income:timespan:period-key')
})
it('should retrieve a measurement total for period key', async () => {
redisClient.get = jest.fn().mockReturnValueOnce(5)
expect(await createStore().getMeasureTotal(StatisticsMeasure.Income, '2022-10-03')).toEqual(5)
expect(redisClient.get).toHaveBeenCalledWith('count:measure:income:timespan:2022-10-03')
})
})

View File

@@ -18,10 +18,13 @@ export class RedisStatisticsStore implements StatisticsStoreInterface {
await pipeline.exec()
}
async getMeasureTotal(measure: StatisticsMeasure, period: Period): Promise<number> {
const totalValue = await this.redisClient.get(
`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
)
async getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number> {
let periodKey = periodOrPeriodKey
if (!isNaN(+periodOrPeriodKey)) {
periodKey = this.periodKeyGenerator.getPeriodKey(periodOrPeriodKey as Period)
}
const totalValue = await this.redisClient.get(`count:measure:${measure}:timespan:${periodKey}`)
if (totalValue === null) {
return 0

View File

@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.24.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.24.4...@standardnotes/api-gateway@1.24.5) (2022-10-04)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.24.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.24.3...@standardnotes/api-gateway@1.24.4) (2022-10-04)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.24.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.24.2...@standardnotes/api-gateway@1.24.3) (2022-10-04)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.24.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.24.1...@standardnotes/api-gateway@1.24.2) (2022-10-03)
### Bug Fixes
* **api-gateway:** report churn values for empty months ([f43fbf1](https://github.com/standardnotes/api-gateway/commit/f43fbf15844be05add905134dfb3e8ca90f78458))
## [1.24.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.24.0...@standardnotes/api-gateway@1.24.1) (2022-10-03)
### Bug Fixes
* add debug logs for churn calculation ([2236cc3](https://github.com/standardnotes/api-gateway/commit/2236cc3828167e4b94defbde2691bba38458bd1c))
# [1.24.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.23.0...@standardnotes/api-gateway@1.24.0) (2022-10-03)
### Features
* add calculating monthly churn rate ([f075cd8](https://github.com/standardnotes/api-gateway/commit/f075cd8c4dfc411ba513dfec21bb84c03b238254))
# [1.23.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.22.6...@standardnotes/api-gateway@1.23.0) (2022-09-30)
### Features

View File

@@ -136,6 +136,39 @@ const requestReport = async (
}
}
const monthlyPeriodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.ThisYear)
const churnRates = []
for (const monthPeriodKey of monthlyPeriodKeys) {
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
const dailyPeriodKeys = periodKeyGenerator.getDiscretePeriodKeys(monthPeriod)
const totalCustomerCounts: Array<number> = []
for (const dailyPeriodKey of dailyPeriodKeys) {
const customersCount = await statisticsStore.getMeasureTotal(StatisticsMeasure.TotalCustomers, dailyPeriodKey)
totalCustomerCounts.push(customersCount)
}
const filteredTotalCustomerCounts = totalCustomerCounts.filter((count) => !!count)
const averageCustomersCount = filteredTotalCustomerCounts.length
? filteredTotalCustomerCounts.reduce((total, current) => total + current, 0) / filteredTotalCustomerCounts.length
: 0
const existingCustomersChurn = await analyticsStore.calculateActivityTotalCount(
AnalyticsActivity.ExistingCustomersChurn,
monthPeriodKey,
)
const newCustomersChurn = await analyticsStore.calculateActivityTotalCount(
AnalyticsActivity.NewCustomersChurn,
monthPeriodKey,
)
const totalChurn = existingCustomersChurn + newCustomersChurn
churnRates.push({
periodKey: monthPeriodKey,
rate: averageCustomersCount ? (totalChurn / averageCustomersCount) * 100 : 0,
})
}
const event: DailyAnalyticsReportGeneratedEvent = {
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
createdAt: new Date(),
@@ -163,6 +196,10 @@ const requestReport = async (
},
},
],
churn: {
periodKeys: monthlyPeriodKeys,
values: churnRates,
},
},
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.23.0",
"version": "1.24.5",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.37.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.36.4...@standardnotes/auth-server@1.37.0) (2022-10-04)
### Features
* **auth:** add detailed income stats ([8668fec](https://github.com/standardnotes/server/commit/8668fec33dac1598bdc4d6ca869c296ed6eaa617))
## [1.36.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.36.3...@standardnotes/auth-server@1.36.4) (2022-10-04)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.36.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.36.2...@standardnotes/auth-server@1.36.3) (2022-10-04)
### Bug Fixes
* **auth:** turn down severity of logs for predicate verification ([09b3f9a](https://github.com/standardnotes/server/commit/09b3f9a0d787d2a329f84e2d625ec8a63b4bd847))
## [1.36.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.36.1...@standardnotes/auth-server@1.36.2) (2022-10-03)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.36.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.36.0...@standardnotes/auth-server@1.36.1) (2022-10-03)
### Bug Fixes
* **auth:** counting active subscriptions ([e7736bb](https://github.com/standardnotes/server/commit/e7736bba250782a3967fd08c82dbf32884b5b892))
# [1.36.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.35.0...@standardnotes/auth-server@1.36.0) (2022-10-03)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.36.0",
"version": "1.37.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -1,7 +1,7 @@
import 'reflect-metadata'
import { PaymentSuccessEvent } from '@standardnotes/domain-events'
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
import { PaymentSuccessEventHandler } from './PaymentSuccessEventHandler'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
@@ -38,14 +38,41 @@ describe('PaymentSuccessEventHandler', () => {
event.payload = {
userEmail: 'test@test.com',
amount: 12.45,
billingFrequency: 12,
paymentType: 'initial',
subscriptionName: 'PRO_PLAN',
}
})
it('should mark payment failed for analytics', async () => {
it('should mark payment success for analytics', async () => {
await createHandler().handle(event)
expect(analyticsStore.markActivity).toHaveBeenCalled()
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
expect(statisticsStore.incrementMeasure).toHaveBeenNthCalledWith(
2,
'pro-subscription-initial-annual-payments-income',
12.45,
[Period.Today, Period.ThisWeek, Period.ThisMonth],
)
})
it('should mark non-detailed payment success statistics for analytics', async () => {
event.payload = {
userEmail: 'test@test.com',
amount: 12.45,
billingFrequency: 13,
paymentType: 'initial',
subscriptionName: 'PRO_PLAN',
}
await createHandler().handle(event)
expect(statisticsStore.incrementMeasure).toBeCalledTimes(1)
expect(statisticsStore.incrementMeasure).toHaveBeenNthCalledWith(1, 'income', 12.45, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
})
it('should not mark payment failed for analytics if user is not found', async () => {

View File

@@ -5,6 +5,7 @@ import {
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { PaymentType, SubscriptionBillingFrequency, SubscriptionName } from '@standardnotes/common'
import { DomainEventHandlerInterface, PaymentSuccessEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
@@ -14,6 +15,47 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
@injectable()
export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
private readonly DETAILED_MEASURES = new Map([
[
SubscriptionName.PlusPlan,
new Map([
[
PaymentType.Initial,
new Map([
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome],
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome],
]),
],
[
PaymentType.Renewal,
new Map([
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome],
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome],
]),
],
]),
],
[
SubscriptionName.ProPlan,
new Map([
[
PaymentType.Initial,
new Map([
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome],
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome],
]),
],
[
PaymentType.Renewal,
new Map([
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome],
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome],
]),
],
]),
],
])
constructor(
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@@ -34,10 +76,21 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
Period.ThisMonth,
])
await this.statisticsStore.incrementMeasure(StatisticsMeasure.Income, event.payload.amount, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
const statisticMeasures = [StatisticsMeasure.Income]
const detailedMeasure = this.DETAILED_MEASURES.get(event.payload.subscriptionName as SubscriptionName)
?.get(event.payload.paymentType as PaymentType)
?.get(event.payload.billingFrequency as SubscriptionBillingFrequency)
if (detailedMeasure !== undefined) {
statisticMeasures.push(detailedMeasure)
}
for (const measure of statisticMeasures) {
await this.statisticsStore.incrementMeasure(measure, event.payload.amount, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
}
}
}

View File

@@ -54,6 +54,7 @@ describe('PredicateVerificationRequestedEventHandler', () => {
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
logger.info = jest.fn()
logger.debug = jest.fn()
event = {} as jest.Mocked<PredicateVerificationRequestedEvent>
event.meta = {

View File

@@ -23,7 +23,7 @@ export class PredicateVerificationRequestedEventHandler implements DomainEventHa
) {}
async handle(event: PredicateVerificationRequestedEvent): Promise<void> {
this.logger.info(`Received verification request of predicate: ${event.payload.predicate.name}`)
this.logger.debug(`Received verification request of predicate: ${event.payload.predicate.name}`)
let userUuid = event.meta.correlation.userIdentifier
if (event.meta.correlation.userIdentifierType === 'email') {
@@ -55,7 +55,7 @@ export class PredicateVerificationRequestedEventHandler implements DomainEventHa
}),
)
this.logger.info(
this.logger.debug(
`Published predicate verification (${predicateVerificationResult}) result for: ${event.payload.predicate.name}`,
)
}

View File

@@ -66,18 +66,16 @@ describe('MySQLUserSubscriptionRepository', () => {
it('should count all active subscriptions', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.select = jest.fn().mockReturnThis()
selectQueryBuilder.distinct = jest.fn().mockReturnThis()
selectQueryBuilder.groupBy = jest.fn().mockReturnThis()
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getCount = jest.fn().mockReturnValue(2)
const result = await createRepository().countActiveSubscriptions()
expect(selectQueryBuilder.select).toHaveBeenCalledWith('user_uuid')
expect(selectQueryBuilder.distinct).toHaveBeenCalled()
expect(selectQueryBuilder.where).toHaveBeenCalledWith('ends_at > :timestamp', {
timestamp: 123,
})
expect(selectQueryBuilder.groupBy).toHaveBeenCalledWith('user_uuid')
expect(selectQueryBuilder.getCount).toHaveBeenCalled()
expect(result).toEqual(2)
})

View File

@@ -19,9 +19,8 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
async countActiveSubscriptions(): Promise<number> {
return await this.ormRepository
.createQueryBuilder()
.select('user_uuid')
.distinct()
.where('ends_at > :timestamp', { timestamp: this.timer.getTimestampInMicroseconds() })
.groupBy('user_uuid')
.getCount()
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.35.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.34.0...@standardnotes/common@1.35.0) (2022-10-04)
### Features
* **auth:** add detailed income stats ([8668fec](https://github.com/standardnotes/server/commit/8668fec33dac1598bdc4d6ca869c296ed6eaa617))
# [1.34.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.33.0...@standardnotes/common@1.34.0) (2022-10-04)
### Features
* **common:** add subscription billing frequency ([3c40ee4](https://github.com/standardnotes/server/commit/3c40ee4b4a33dffc35da148a0fa1582c08619733))
# [1.33.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.32.0...@standardnotes/common@1.33.0) (2022-09-19)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.33.0",
"version": "1.35.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -0,0 +1,5 @@
/* istanbul ignore file */
export enum PaymentType {
Initial = 'initial',
Renewal = 'renewal',
}

View File

@@ -0,0 +1,5 @@
/* istanbul ignore file */
export enum SubscriptionBillingFrequency {
Monthly = 1,
Annual = 12,
}

View File

@@ -14,9 +14,11 @@ export * from './KeyParams/KeyParamsContent002'
export * from './KeyParams/KeyParamsContent003'
export * from './KeyParams/KeyParamsContent004'
export * from './KeyParams/KeyParamsOrigination'
export * from './Payment/PaymentType'
export * from './Protocol/ProtocolVersion'
export * from './Role/PaidRoles'
export * from './Role/RoleName'
export * from './Subscription/SubscriptionBillingFrequency'
export * from './Subscription/SubscriptionName'
export * from './Type/Either'
export * from './Type/Only'

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.16](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.15...@standardnotes/domain-events-infra@1.8.16) (2022-10-04)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.15](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.14...@standardnotes/domain-events-infra@1.8.15) (2022-10-04)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.14](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.13...@standardnotes/domain-events-infra@1.8.14) (2022-10-03)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.13](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.12...@standardnotes/domain-events-infra@1.8.13) (2022-09-28)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.8.13",
"version": "1.8.16",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.62.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.61.1...@standardnotes/domain-events@2.62.0) (2022-10-04)
### Features
* **auth:** add detailed income stats ([8668fec](https://github.com/standardnotes/server/commit/8668fec33dac1598bdc4d6ca869c296ed6eaa617))
## [2.61.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.61.0...@standardnotes/domain-events@2.61.1) (2022-10-04)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.61.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.7...@standardnotes/domain-events@2.61.0) (2022-10-03)
### Features
* add calculating monthly churn rate ([f075cd8](https://github.com/standardnotes/server/commit/f075cd8c4dfc411ba513dfec21bb84c03b238254))
## [2.60.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.6...@standardnotes/domain-events@2.60.7) (2022-09-28)
**Note:** Version bump only for package @standardnotes/domain-events

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.60.7",
"version": "2.62.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -40,4 +40,11 @@ export interface DailyAnalyticsReportGeneratedEventPayload {
}>
}
}>
churn: {
periodKeys: Array<string>
values: Array<{
rate: number
periodKey: string
}>
}
}

View File

@@ -1,4 +1,7 @@
export interface PaymentSuccessEventPayload {
userEmail: string
amount: number
billingFrequency: number
paymentType: string
subscriptionName: string
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.21](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.20...@standardnotes/event-store@1.3.21) (2022-10-04)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.20](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.19...@standardnotes/event-store@1.3.20) (2022-10-04)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.19](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.18...@standardnotes/event-store@1.3.19) (2022-10-03)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.18](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.17...@standardnotes/event-store@1.3.18) (2022-09-28)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.3.18",
"version": "1.3.21",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.7](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.6...@standardnotes/files-server@1.6.7) (2022-10-04)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.6](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.5...@standardnotes/files-server@1.6.6) (2022-10-04)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.4...@standardnotes/files-server@1.6.5) (2022-10-03)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.3...@standardnotes/files-server@1.6.4) (2022-09-28)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.6.4",
"version": "1.6.7",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.4](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.3...@standardnotes/predicates@1.4.4) (2022-10-04)
**Note:** Version bump only for package @standardnotes/predicates
## [1.4.3](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.2...@standardnotes/predicates@1.4.3) (2022-10-04)
**Note:** Version bump only for package @standardnotes/predicates
## [1.4.2](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.1...@standardnotes/predicates@1.4.2) (2022-09-19)
**Note:** Version bump only for package @standardnotes/predicates

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/predicates",
"version": "1.4.2",
"version": "1.4.4",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.35](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.34...@standardnotes/scheduler-server@1.10.35) (2022-10-04)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.34](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.33...@standardnotes/scheduler-server@1.10.34) (2022-10-04)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.33](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.32...@standardnotes/scheduler-server@1.10.33) (2022-10-03)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.32](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.31...@standardnotes/scheduler-server@1.10.32) (2022-09-28)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.10.32",
"version": "1.10.35",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.2](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.1...@standardnotes/security@1.4.2) (2022-10-04)
**Note:** Version bump only for package @standardnotes/security
## [1.4.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.4.0...@standardnotes/security@1.4.1) (2022-10-04)
**Note:** Version bump only for package @standardnotes/security
# [1.4.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.3.3...@standardnotes/security@1.4.0) (2022-09-21)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.4.0",
"version": "1.4.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.19](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.18...@standardnotes/syncing-server@1.8.19) (2022-10-04)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.8.18](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.17...@standardnotes/syncing-server@1.8.18) (2022-10-04)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.8.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.16...@standardnotes/syncing-server@1.8.17) (2022-10-04)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.8.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.15...@standardnotes/syncing-server@1.8.16) (2022-10-03)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.8.15](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.14...@standardnotes/syncing-server@1.8.15) (2022-09-30)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.8.15",
"version": "1.8.19",
"engines": {
"node": ">=16.0.0 <17.0.0"
},