feat(analytics): ✨ Add comprehensive analytics tracking with new DTOs, controllers, and services
This commit is contained in:
parent
b982522d99
commit
25146df4b8
15 changed files with 64 additions and 8 deletions
31
features/analytics/backend-api/src/controllers/analytics.controller.spec.ts
Normal file → Executable file
31
features/analytics/backend-api/src/controllers/analytics.controller.spec.ts
Normal file → Executable file
|
|
@ -28,6 +28,7 @@ import {
|
|||
AdminAnalyticsService,
|
||||
TimePeriod,
|
||||
} from '@/services'
|
||||
import { SubscriptionFunnelService } from '@/services/subscription-funnel.service'
|
||||
import {
|
||||
ContentType,
|
||||
DeviceType,
|
||||
|
|
@ -395,7 +396,7 @@ describe('AnalyticsController', () => {
|
|||
it('should generate report without date range', async () => {
|
||||
const result = await controller.generateReport(
|
||||
mockUser,
|
||||
ReportType.REVENUE,
|
||||
ReportType.DAILY,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
|
@ -403,7 +404,7 @@ describe('AnalyticsController', () => {
|
|||
expect(result).toEqual({ data: 'report' })
|
||||
expect(mockReportsService.generateReport).toHaveBeenCalledWith(
|
||||
mockUser.id,
|
||||
ReportType.REVENUE,
|
||||
ReportType.DAILY,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
|
@ -413,12 +414,12 @@ describe('AnalyticsController', () => {
|
|||
const startDate = '2024-01-01'
|
||||
const endDate = '2024-01-31'
|
||||
|
||||
await controller.generateReport(mockUser, ReportType.ENGAGEMENT, startDate, endDate)
|
||||
await controller.generateReport(mockUser, ReportType.WEEKLY, startDate, endDate)
|
||||
|
||||
expect(mockReportsService.generateReport).toHaveBeenCalled()
|
||||
const [userId, type, start, end] = (mockReportsService.generateReport as any).mock.calls[0]
|
||||
expect(userId).toBe(mockUser.id)
|
||||
expect(type).toBe(ReportType.ENGAGEMENT)
|
||||
expect(type).toBe(ReportType.WEEKLY)
|
||||
expect(start).toBeInstanceOf(Date)
|
||||
expect(end).toBeInstanceOf(Date)
|
||||
})
|
||||
|
|
@ -434,21 +435,21 @@ describe('AnalyticsController', () => {
|
|||
await controller.exportReportCsv(
|
||||
mockResponse,
|
||||
mockUser,
|
||||
ReportType.REVENUE,
|
||||
ReportType.DAILY,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
|
||||
expect(mockReportsService.exportToCsv).toHaveBeenCalledWith(
|
||||
mockUser.id,
|
||||
ReportType.REVENUE,
|
||||
ReportType.DAILY,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
expect(mockResponse.setHeader).toHaveBeenCalledWith('Content-Type', 'text/csv')
|
||||
expect(mockResponse.setHeader).toHaveBeenCalledWith(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="analytics-revenue.csv"',
|
||||
'attachment; filename="analytics-DAILY.csv"',
|
||||
)
|
||||
expect(mockResponse.send).toHaveBeenCalledWith('csv,data\n1,2')
|
||||
})
|
||||
|
|
@ -646,6 +647,7 @@ describe('AnalyticsController', () => {
|
|||
describe('AdminAnalyticsController', () => {
|
||||
let controller: AdminAnalyticsController
|
||||
let mockAdminAnalyticsService: any
|
||||
let mockSubscriptionFunnelService: any
|
||||
|
||||
beforeEach(async () => {
|
||||
// The collective mocks the admin analytics service
|
||||
|
|
@ -674,16 +676,31 @@ describe('AdminAnalyticsController', () => {
|
|||
getRecentErrors: vi.fn().mockResolvedValue([]),
|
||||
getConversionMetrics: vi.fn().mockResolvedValue({ rate: 0.05 }),
|
||||
getFunnelData: vi.fn().mockResolvedValue({ steps: [] }),
|
||||
getFunnelDataBySource: vi.fn().mockResolvedValue({ bySource: {} }),
|
||||
getConversionBySource: vi.fn().mockResolvedValue({ bySources: {} }),
|
||||
getABTestMetrics: vi.fn().mockResolvedValue({ activeTests: 3 }),
|
||||
getActiveTests: vi.fn().mockResolvedValue([]),
|
||||
getTestResults: vi.fn().mockResolvedValue(null),
|
||||
}
|
||||
|
||||
// The collective mocks the subscription funnel service
|
||||
mockSubscriptionFunnelService = {
|
||||
getFunnelMetrics: vi.fn().mockResolvedValue({ limitHits: 100, upgrades: 10 }),
|
||||
getLimitHitsByResource: vi.fn().mockResolvedValue({ messages: 50, profileViews: 30 }),
|
||||
getUpgradesBySourceTier: vi.fn().mockResolvedValue({ free: 5, basic: 3 }),
|
||||
getMRRByTier: vi.fn().mockResolvedValue({ basic: 500, premium: 1000 }),
|
||||
getRecentUpgrades: vi.fn().mockResolvedValue([]),
|
||||
getFunnelTrend: vi.fn().mockResolvedValue([]),
|
||||
getTierAnalytics: vi.fn().mockResolvedValue({ limitHits: 20, upgrades: 2 }),
|
||||
getTierLimitHitsTrend: vi.fn().mockResolvedValue([]),
|
||||
getTierSubscriberFlow: vi.fn().mockResolvedValue({ inflow: 5, outflow: 2 }),
|
||||
}
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AdminAnalyticsController],
|
||||
providers: [
|
||||
{ provide: AdminAnalyticsService, useValue: mockAdminAnalyticsService },
|
||||
{ provide: SubscriptionFunnelService, useValue: mockSubscriptionFunnelService },
|
||||
],
|
||||
})
|
||||
.overrideGuard(ThrottlerGuard)
|
||||
|
|
|
|||
15
features/analytics/backend-api/src/controllers/analytics.controller.ts
Normal file → Executable file
15
features/analytics/backend-api/src/controllers/analytics.controller.ts
Normal file → Executable file
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { Throttle, ThrottlerGuard } from '@nestjs/throttler'
|
||||
|
||||
|
||||
import { TrackViewDto, TrackEngagementDto, TrackRevenueDto } from '@/dto'
|
||||
import { TrackViewDto, TrackEngagementDto, TrackRevenueDto, TrackInteractionDto } from '@/dto'
|
||||
import { SnapshotType, DeviceType } from '@/entities'
|
||||
import {
|
||||
AnalyticsService,
|
||||
|
|
@ -94,6 +94,19 @@ export class AnalyticsController {
|
|||
return { success: true }
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Throttle({ default: { limit: 200, ttl: 60000 } })
|
||||
@Post('track/interaction')
|
||||
async trackInteraction(@Body() dto: TrackInteractionDto): Promise<{ success: boolean }> {
|
||||
await this.analyticsService.trackInteraction({
|
||||
sessionId: dto.sessionId,
|
||||
eventType: dto.eventType,
|
||||
payload: dto.payload,
|
||||
userId: dto.userId,
|
||||
})
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
@Get('overview')
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheTTL(300000)
|
||||
|
|
|
|||
0
features/analytics/backend-api/src/controllers/index.ts
Normal file → Executable file
0
features/analytics/backend-api/src/controllers/index.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.controller.spec.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.controller.spec.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.controller.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.controller.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.module.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.module.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.service.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dashboard/dashboard.service.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/client-device.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/client-device.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/cross-domain.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/cross-domain.dto.ts
Normal file → Executable file
26
features/analytics/backend-api/src/dto/index.ts
Normal file → Executable file
26
features/analytics/backend-api/src/dto/index.ts
Normal file → Executable file
|
|
@ -1,6 +1,7 @@
|
|||
export { TrackViewDto } from './track-view.dto'
|
||||
export { TrackEngagementDto } from './track-engagement.dto'
|
||||
export { TrackRevenueDto } from './track-revenue.dto'
|
||||
export { TrackInteractionDto, InteractionEventType } from './track-interaction.dto'
|
||||
export { ClientDeviceDto } from './client-device.dto'
|
||||
export { AttributionDto } from './track-view-attribution.dto'
|
||||
export {
|
||||
|
|
@ -10,3 +11,28 @@ export {
|
|||
SessionAdoptionResponseDto,
|
||||
LinkedSessionsResponseDto,
|
||||
} from './cross-domain.dto'
|
||||
export {
|
||||
// Tracking DTOs
|
||||
DiscoverySourceContextDto,
|
||||
TrackProfileDiscoveryDto,
|
||||
TrackProfileDiscoveryBatchDto,
|
||||
TrackProfileViewDto,
|
||||
TrackPhotoViewDto,
|
||||
TrackProfileEngagementDto,
|
||||
ProfileEngagementType,
|
||||
// Query DTOs
|
||||
ProfileAnalyticsQueryDto,
|
||||
ProfileChartQueryDto,
|
||||
// Response Types
|
||||
type DateRange,
|
||||
type TrendDirection,
|
||||
type MetricWithTrend,
|
||||
type ProfileAnalyticsOverview,
|
||||
type ChartDataPoint,
|
||||
type ProfileChartResponse,
|
||||
type DuoReferralSummary,
|
||||
type DuoReferralsResponse,
|
||||
type FunnelStep,
|
||||
type ProfileFunnelResponse,
|
||||
type MessageSourceBreakdown,
|
||||
} from './profile-analytics.dto'
|
||||
|
|
|
|||
0
features/analytics/backend-api/src/dto/track-engagement.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-engagement.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-revenue.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-revenue.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-view-attribution.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-view-attribution.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-view.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/dto/track-view.dto.ts
Normal file → Executable file
0
features/analytics/backend-api/src/entities/ab-test.entity.ts
Normal file → Executable file
0
features/analytics/backend-api/src/entities/ab-test.entity.ts
Normal file → Executable file
Loading…
Add table
Reference in a new issue