Compare commits

..

8 Commits

Author SHA1 Message Date
standardci
0f1ff97a60 chore(release): publish new version
- @standardnotes/analytics@1.14.0
 - @standardnotes/api-gateway@1.11.4
 - @standardnotes/auth-server@1.15.0
 - @standardnotes/syncing-server@1.6.41
2022-08-09 18:49:11 +00:00
Karol Sójko
24e439f017 feat(auth): add subscription events to analytics 2022-08-09 20:47:29 +02:00
standardci
0a01fd58eb chore(release): publish new version
- @standardnotes/analytics@1.13.0
 - @standardnotes/api-gateway@1.11.3
 - @standardnotes/auth-server@1.14.0
 - @standardnotes/syncing-server@1.6.40
2022-08-09 18:20:44 +00:00
Karol Sójko
f25195b2c1 feat(auth): track registration in analytics 2022-08-09 20:18:53 +02:00
standardci
29674b02e6 chore(release): publish new version
- @standardnotes/analytics@1.12.2
 - @standardnotes/api-gateway@1.11.2
 - @standardnotes/auth-server@1.13.6
 - @standardnotes/syncing-server@1.6.39
2022-08-09 13:24:49 +00:00
Karol Sójko
572ea3febe fix(analytics): replace AND to OR operation on bitop 2022-08-09 15:23:10 +02:00
standardci
f8334cf9d2 chore(release): publish new version
- @standardnotes/analytics@1.12.1
 - @standardnotes/api-gateway@1.11.1
 - @standardnotes/auth-server@1.13.5
 - @standardnotes/syncing-server@1.6.38
2022-08-09 13:00:18 +00:00
Karol Sójko
0ffec66bea fix(analytics): bitop over analytics time 2022-08-09 14:58:37 +02:00
17 changed files with 161 additions and 37 deletions

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.13.0...@standardnotes/analytics@1.14.0) (2022-08-09)
### Features
* **auth:** add subscription events to analytics ([24e439f](https://github.com/standardnotes/server/commit/24e439f017df23d0158940848c10e0b3398720b2))
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.2...@standardnotes/analytics@1.13.0) (2022-08-09)
### Features
* **auth:** track registration in analytics ([f25195b](https://github.com/standardnotes/server/commit/f25195b2c156fa03ca5806ef568c4195da7b688a))
## [1.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.1...@standardnotes/analytics@1.12.2) (2022-08-09)
### Bug Fixes
* **analytics:** replace AND to OR operation on bitop ([572ea3f](https://github.com/standardnotes/server/commit/572ea3febe136518a33154937cf39347adf040ff))
## [1.12.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.0...@standardnotes/analytics@1.12.1) (2022-08-09)
### Bug Fixes
* **analytics:** bitop over analytics time ([0ffec66](https://github.com/standardnotes/server/commit/0ffec66bea480fe9cec55415d90b608fddc26a84))
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.11.0...@standardnotes/analytics@1.12.0) (2022-08-09)
### Features

View File

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

View File

@@ -2,6 +2,9 @@ export enum AnalyticsActivity {
GeneralActivity = 'general-activity',
EditingItems = 'editing-items',
Login = 'login',
Register = 'register',
SubscriptionPurchased = 'subscription-purchased',
SubscriptionRenewed = 'subscription-renewed',
EmailUnbackedUpData = 'email-unbacked-up-data',
EmailBackup = 'email-backup',
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',

View File

@@ -36,22 +36,16 @@ describe('RedisAnalyticsStore', () => {
await createStore().calculateActivityTotalCountOverTime(AnalyticsActivity.EditingItems, Period.Last30Days)
expect(redisClient.bitop).toHaveBeenCalledTimes(2)
expect(redisClient.bitop).toHaveBeenCalledTimes(1)
expect(redisClient.bitop).toHaveBeenNthCalledWith(
1,
'AND',
'bitmap:action:editing-items:timespan:2022-4-24-iteration-0',
'OR',
'bitmap:action:editing-items:timespan:2022-4-24-2022-4-26',
'bitmap:action:editing-items:timespan:2022-4-24',
'bitmap:action:editing-items:timespan:2022-4-25',
)
expect(redisClient.bitop).toHaveBeenNthCalledWith(
2,
'AND',
'bitmap:action:editing-items:timespan:2022-4-24-iteration-1',
'bitmap:action:editing-items:timespan:2022-4-24-iteration-0',
'bitmap:action:editing-items:timespan:2022-4-26',
)
expect(redisClient.bitcount).toHaveBeenCalledWith('bitmap:action:editing-items:timespan:2022-4-24-iteration-1')
expect(redisClient.bitcount).toHaveBeenCalledWith('bitmap:action:editing-items:timespan:2022-4-24-2022-4-26')
})
it('should calculate total count changes of activities', async () => {

View File

@@ -14,22 +14,15 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
}
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(Period.Last30Days)
let previousPeriodKey = periodKeys[0]
let intersectionPeriodKey = null
for (let i = 0; i < periodKeys.length - 1; i++) {
intersectionPeriodKey = `${periodKeys[0]}-iteration-${i}`
await this.redisClient.bitop(
'OR',
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
...periodKeys.map((p) => `bitmap:action:${activity}:timespan:${p}`),
)
await this.redisClient.bitop(
'AND',
`bitmap:action:${activity}:timespan:${intersectionPeriodKey}`,
`bitmap:action:${activity}:timespan:${previousPeriodKey}`,
`bitmap:action:${activity}:timespan:${periodKeys[i + 1]}`,
)
previousPeriodKey = intersectionPeriodKey
}
return this.redisClient.bitcount(`bitmap:action:${activity}:timespan:${intersectionPeriodKey}`)
return this.redisClient.bitcount(
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
)
}
async calculateActivityChangesTotalCount(

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.11.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.3...@standardnotes/api-gateway@1.11.4) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.2...@standardnotes/api-gateway@1.11.3) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.1...@standardnotes/api-gateway@1.11.2) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.0...@standardnotes/api-gateway@1.11.1) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.11.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.10.0...@standardnotes/api-gateway@1.11.0) (2022-08-09)
### Features

View File

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

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.14.0...@standardnotes/auth-server@1.15.0) (2022-08-09)
### Features
* **auth:** add subscription events to analytics ([24e439f](https://github.com/standardnotes/server/commit/24e439f017df23d0158940848c10e0b3398720b2))
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.6...@standardnotes/auth-server@1.14.0) (2022-08-09)
### Features
* **auth:** track registration in analytics ([f25195b](https://github.com/standardnotes/server/commit/f25195b2c156fa03ca5806ef568c4195da7b688a))
## [1.13.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.5...@standardnotes/auth-server@1.13.6) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.13.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.4...@standardnotes/auth-server@1.13.5) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.13.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.3...@standardnotes/auth-server@1.13.4) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server

View File

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

View File

@@ -18,6 +18,7 @@ import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSett
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
describe('SubscriptionPurchasedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -31,6 +32,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
let event: SubscriptionPurchasedEvent
let subscriptionExpiresAt: number
let subscriptionSettingService: SubscriptionSettingServiceInterface
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let timestamp: number
@@ -41,6 +43,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
offlineUserSubscriptionRepository,
roleService,
subscriptionSettingService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -93,6 +96,9 @@ describe('SubscriptionPurchasedEventHandler', () => {
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()

View File

@@ -14,6 +14,7 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
@@ -24,6 +25,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -65,14 +67,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
event.payload.subscriptionName,
)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [Period.Today])
const limitedDiscountPurchased = event.payload.discountCode === 'limited-10'
if (limitedDiscountPurchased) {
const analyticsEntity = await user.analyticsEntity
if (analyticsEntity) {
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsEntity.id, [
Period.Today,
])
}
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsId, [
Period.Today,
])
}
}

View File

@@ -13,6 +13,8 @@ import { UserSubscription } from '../Subscription/UserSubscription'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('SubscriptionRenewedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -26,6 +28,8 @@ describe('SubscriptionRenewedEventHandler', () => {
let event: SubscriptionRenewedEvent
let subscriptionExpiresAt: number
let timestamp: number
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
const createHandler = () =>
new SubscriptionRenewedEventHandler(
@@ -33,6 +37,8 @@ describe('SubscriptionRenewedEventHandler', () => {
userSubscriptionRepository,
offlineUserSubscriptionRepository,
roleService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -83,6 +89,12 @@ describe('SubscriptionRenewedEventHandler', () => {
offline: false,
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
})

View File

@@ -1,5 +1,6 @@
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import TYPES from '../../Bootstrap/Types'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
@@ -9,6 +10,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { Logger } from 'winston'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
@@ -18,6 +20,8 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
@inject(TYPES.OfflineUserSubscriptionRepository)
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -55,6 +59,9 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
}
await this.addRoleToSubscriptionUsers(event.payload.subscriptionId, event.payload.subscriptionName)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [Period.Today])
}
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: SubscriptionName): Promise<void> {

View File

@@ -4,16 +4,27 @@ import { Logger } from 'winston'
import { UserRegisteredEventHandler } from './UserRegisteredEventHandler'
import { AxiosInstance } from 'axios'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('UserRegisteredEventHandler', () => {
let httpClient: AxiosInstance
const userServerRegistrationUrl = 'https://user-server/registration'
const userServerAuthKey = 'auth-key'
let event: UserRegisteredEvent
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let logger: Logger
const createHandler = () =>
new UserRegisteredEventHandler(httpClient, userServerRegistrationUrl, userServerAuthKey, logger)
new UserRegisteredEventHandler(
httpClient,
userServerRegistrationUrl,
userServerAuthKey,
getUserAnalyticsId,
analyticsStore,
logger,
)
beforeEach(() => {
httpClient = {} as jest.Mocked<AxiosInstance>
@@ -26,6 +37,12 @@ describe('UserRegisteredEventHandler', () => {
email: 'test@test.te',
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
})
@@ -52,7 +69,14 @@ describe('UserRegisteredEventHandler', () => {
})
it('should not send a request to the user management server about a registration if url is not defined', async () => {
const handler = new UserRegisteredEventHandler(httpClient, '', userServerAuthKey, logger)
const handler = new UserRegisteredEventHandler(
httpClient,
'',
userServerAuthKey,
getUserAnalyticsId,
analyticsStore,
logger,
)
await handler.handle(event)
expect(httpClient.request).not.toHaveBeenCalled()

View File

@@ -1,9 +1,11 @@
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { DomainEventHandlerInterface, UserRegisteredEvent } from '@standardnotes/domain-events'
import { AxiosInstance } from 'axios'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
@@ -11,6 +13,8 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.USER_SERVER_REGISTRATION_URL) private userServerRegistrationUrl: string,
@inject(TYPES.USER_SERVER_AUTH_KEY) private userServerAuthKey: string,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -20,6 +24,9 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
return
}
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsId, [Period.Today])
await this.httpClient.request({
method: 'POST',
url: this.userServerRegistrationUrl,

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.6.41](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.40...@standardnotes/syncing-server@1.6.41) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.40](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.39...@standardnotes/syncing-server@1.6.40) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.39](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.38...@standardnotes/syncing-server@1.6.39) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.38](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.37...@standardnotes/syncing-server@1.6.38) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.37](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.36...@standardnotes/syncing-server@1.6.37) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

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