Skip to content

Commit 70b096b

Browse files
authored
Merge pull request #587 from Swetrix/feature/v2-analytics-api
feat: Per-dimension stats API
2 parents 64b1af2 + 0875509 commit 70b096b

180 files changed

Lines changed: 27675 additions & 12289 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,4 @@ DESIGN.md
7878
DESIGN.json
7979

8080
next-env.d.ts
81+
**/tsconfig.tsbuildinfo

backend/apps/cloud/src/analytics/analytics.controller.ts

Lines changed: 16 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ import {
4343
DataType,
4444
getLowestPossibleTimeBucket,
4545
} from './analytics.service'
46+
import { normalizeFiltersToV1Json } from './v2/query/filters.translator'
4647
import { VALID_PERIODS } from './decorators/validate-period.decorator'
4748
import { CurrentUserId } from '../auth/decorators/current-user-id.decorator'
4849
import { DEFAULT_TIMEZONE } from '../user/entities/user.entity'
4950
import { AuthenticationGuard } from '../auth/guards/authentication.guard'
5051
import { PageviewsDto } from './dto/pageviews.dto'
5152
import { EventsDto } from './dto/events.dto'
52-
import { GetDataDto, ChartRenderMode, TimeBucketType } from './dto/getData.dto'
53+
import { GetDataDto, ChartRenderMode } from './dto/getData.dto'
5354
import { GetCustomEventMetadata } from './dto/get-custom-event-meta.dto'
5455
import { GetPagePropertyMetaDto } from './dto/get-page-property-meta.dto'
55-
import { GetUserFlowDto } from './dto/getUserFlow.dto'
5656
import { GetJourneysDto } from './dto/get-journeys.dto'
5757
import { GetJourneySessionsDto } from './dto/get-journey-sessions.dto'
5858
import { GetFunnelsDto } from './dto/getFunnels.dto'
@@ -76,13 +76,7 @@ import {
7676
DataDeletionPreviewDto,
7777
} from './dto/data-deletion.dto'
7878
import { GetVersionFiltersDto } from './dto/get-version-filters.dto'
79-
import {
80-
IFunnel,
81-
IGetFunnel,
82-
IPageProperty,
83-
IUserFlow,
84-
PerfMeasure,
85-
} from './interfaces'
79+
import { IFunnel, IGetFunnel, IPageProperty, PerfMeasure } from './interfaces'
8680
import { GetSessionsDto } from './dto/get-sessions.dto'
8781
import { GetSessionDto } from './dto/get-session.dto'
8882
import {
@@ -115,7 +109,7 @@ import { LiveVisitorsDto } from './dto/live-visitors.dto'
115109
import { GetHeartbeatStatsDto } from './dto/get-heartbeat-stats'
116110
import { GetKeywordsDto } from './dto/get-keywords.dto'
117111
import { GetBotStatsDto } from './dto/get-bot-stats.dto'
118-
import { GSCService } from '../project/gsc.service'
112+
import { GSC_ALL_TIME_DAYS, GSCService } from '../project/gsc.service'
119113
import { GetProfileIdDto, GetSessionIdDto } from './dto/get-id.dto'
120114
import { ExperimentService } from '../experiment/experiment.service'
121115
import {
@@ -135,9 +129,6 @@ const BOT_RESPONSE = { message: 'Bot traffic detected, request is ignored' }
135129

136130
const ONLINE_VISITORS_WINDOW_MINUTES = 5 // minutes
137131

138-
// Maximum range for "all time" GSC queries (~16 months, GSC's data retention)
139-
const GSC_ALL_TIME_DAYS = 480
140-
141132
// Performance object validator: none of the values cannot be bigger than 1000 * 60 * 5 (5 minutes) and are >= 0
142133
const MAX_PERFORMANCE_VALUE = 1000 * 60 * 5
143134
const isPerformanceValid = (perf: any) => {
@@ -1022,59 +1013,6 @@ export class AnalyticsController {
10221013
return this.getData(data, uid, {}, true)
10231014
}
10241015

1025-
@Get('user-flow')
1026-
@Auth(true, true)
1027-
async getUserFlow(
1028-
@Query() data: GetUserFlowDto,
1029-
@CurrentUserId() uid: string,
1030-
@Headers() headers: { 'x-password'?: string },
1031-
): Promise<IUserFlow | { appliedFilters: any[] }> {
1032-
const { pid, period, from, to, timezone = DEFAULT_TIMEZONE, filters } = data
1033-
1034-
await this.analyticsService.checkProjectAccess(
1035-
pid,
1036-
uid,
1037-
headers['x-password'],
1038-
)
1039-
1040-
await this.analyticsService.checkBillingAccess(pid)
1041-
1042-
this.logger.log(
1043-
`pid: ${pid}, period: ${period}`,
1044-
'GET /analytics/user-flow',
1045-
)
1046-
1047-
let diff
1048-
1049-
if (period === 'all') {
1050-
const res = await this.analyticsService.calculateTimeBucketForAllTime(
1051-
pid,
1052-
'pageview',
1053-
)
1054-
1055-
diff = res.diff
1056-
}
1057-
1058-
const safeTimezone = this.analyticsService.getSafeTimezone(timezone)
1059-
const { groupFrom, groupTo } = this.analyticsService.getGroupFromTo(
1060-
from,
1061-
to,
1062-
null,
1063-
period,
1064-
safeTimezone,
1065-
diff,
1066-
)
1067-
1068-
const [filtersQuery, filtersParams, appliedFilters] =
1069-
this.analyticsService.getFiltersQuery(filters, DataType.ANALYTICS, true)
1070-
1071-
const params = { pid, groupFrom, groupTo, ...filtersParams }
1072-
1073-
const flow = await this.analyticsService.getUserFlow(params, filtersQuery)
1074-
1075-
return { ...flow, appliedFilters }
1076-
}
1077-
10781016
@Get('journeys')
10791017
@Auth(true, true)
10801018
async getJourneys(
@@ -1122,7 +1060,11 @@ export class AnalyticsController {
11221060
)
11231061

11241062
const [filtersQuery, filtersParams, appliedFilters] =
1125-
this.analyticsService.getFiltersQuery(filters, DataType.ANALYTICS, true)
1063+
this.analyticsService.getFiltersQuery(
1064+
normalizeFiltersToV1Json(filters, 'traffic'),
1065+
DataType.ANALYTICS,
1066+
true,
1067+
)
11261068

11271069
const params = { pid, groupFrom, groupTo, ...filtersParams }
11281070

@@ -1198,7 +1140,7 @@ export class AnalyticsController {
11981140
)
11991141

12001142
const [filtersQuery, filtersParams] = this.analyticsService.getFiltersQuery(
1201-
filters || '[]',
1143+
normalizeFiltersToV1Json(filters, 'traffic'),
12021144
DataType.ANALYTICS,
12031145
)
12041146
const params = { pid, groupFrom, groupTo, ...filtersParams }
@@ -1251,139 +1193,6 @@ export class AnalyticsController {
12511193
return { keywords }
12521194
}
12531195

1254-
@Get('gsc-dashboard')
1255-
@Auth(true, true)
1256-
async getGSCDashboard(
1257-
@Query() data: GetKeywordsDto,
1258-
@CurrentUserId() uid: string,
1259-
@Headers() headers: { 'x-password'?: string },
1260-
) {
1261-
const {
1262-
pid,
1263-
period,
1264-
from,
1265-
to,
1266-
timezone = DEFAULT_TIMEZONE,
1267-
timeBucket,
1268-
filters,
1269-
} = data
1270-
1271-
await this.analyticsService.checkProjectAccess(
1272-
pid,
1273-
uid,
1274-
headers['x-password'],
1275-
)
1276-
1277-
await this.analyticsService.checkBillingAccess(pid)
1278-
1279-
const defaultTimeBucket = ['1h', 'today', 'yesterday', '1d'].includes(
1280-
period,
1281-
)
1282-
? TimeBucketType.HOUR
1283-
: TimeBucketType.DAY
1284-
const finalTimeBucket = (timeBucket || defaultTimeBucket) as TimeBucketType
1285-
let groupTimeBucket: TimeBucketType | null = finalTimeBucket
1286-
if (period === 'all' || period === 'custom') {
1287-
groupTimeBucket = null
1288-
}
1289-
1290-
this.logger.log(
1291-
`pid: ${pid}, period: ${period}, timeBucket: ${finalTimeBucket}, filters: ${filters}`,
1292-
'GET /analytics/gsc-dashboard',
1293-
)
1294-
1295-
const diff = period === 'all' ? GSC_ALL_TIME_DAYS : undefined
1296-
1297-
const safeTimezone = this.analyticsService.getSafeTimezone(timezone)
1298-
const { groupFrom, groupTo } = this.analyticsService.getGroupFromTo(
1299-
from,
1300-
to,
1301-
groupTimeBucket,
1302-
period,
1303-
safeTimezone,
1304-
diff,
1305-
)
1306-
1307-
return this.gscService.getDashboard(
1308-
pid,
1309-
groupFrom,
1310-
groupTo,
1311-
finalTimeBucket,
1312-
filters,
1313-
)
1314-
}
1315-
1316-
@Get('gsc-details')
1317-
@Auth(true, true)
1318-
async getGSCDetails(
1319-
@Query() data: GetKeywordsDto,
1320-
@CurrentUserId() uid: string,
1321-
@Headers() headers: { 'x-password'?: string },
1322-
) {
1323-
const {
1324-
pid,
1325-
period,
1326-
from,
1327-
to,
1328-
timezone = DEFAULT_TIMEZONE,
1329-
page,
1330-
query,
1331-
} = data
1332-
1333-
this.logger.log(
1334-
`pid: ${pid}, period: ${period}, page: ${page}, query: ${query}`,
1335-
'GET /analytics/gsc-details',
1336-
)
1337-
1338-
await this.analyticsService.checkProjectAccess(
1339-
pid,
1340-
uid,
1341-
headers['x-password'],
1342-
)
1343-
1344-
await this.analyticsService.checkBillingAccess(pid)
1345-
1346-
const diff = period === 'all' ? GSC_ALL_TIME_DAYS : undefined
1347-
1348-
const safeTimezone = this.analyticsService.getSafeTimezone(timezone)
1349-
const { groupFrom, groupTo } = this.analyticsService.getGroupFromTo(
1350-
from,
1351-
to,
1352-
null,
1353-
period,
1354-
safeTimezone,
1355-
diff,
1356-
)
1357-
1358-
if (page) {
1359-
const keywords = await this.gscService.getKeywords(
1360-
pid,
1361-
groupFrom,
1362-
groupTo,
1363-
50,
1364-
0,
1365-
undefined,
1366-
page,
1367-
)
1368-
return { type: 'queries', data: keywords }
1369-
}
1370-
1371-
if (query) {
1372-
const pages = await this.gscService.getTopPages(
1373-
pid,
1374-
groupFrom,
1375-
groupTo,
1376-
50,
1377-
0,
1378-
undefined,
1379-
query,
1380-
)
1381-
return { type: 'pages', data: pages }
1382-
}
1383-
1384-
return { type: 'none', data: [] }
1385-
}
1386-
13871196
@Get('birdseye')
13881197
@Auth(true, true)
13891198
// returns overall short statistics per project
@@ -2578,7 +2387,10 @@ export class AnalyticsController {
25782387
)
25792388

25802389
const [filtersQuery, filtersParams, appliedFilters, customEVFilterApplied] =
2581-
this.analyticsService.getFiltersQuery(filters, DataType.ANALYTICS)
2390+
this.analyticsService.getFiltersQuery(
2391+
normalizeFiltersToV1Json(filters, 'traffic'),
2392+
DataType.ANALYTICS,
2393+
)
25822394

25832395
let timeBucket
25842396
let diff
@@ -3044,7 +2856,7 @@ export class AnalyticsController {
30442856

30452857
return this.analyticsService.getDataDeletionPreview(
30462858
body.pid,
3047-
body.filters || '[]',
2859+
normalizeFiltersToV1Json(body.filters, 'traffic'),
30482860
body.from || null,
30492861
body.to || null,
30502862
)
@@ -3068,7 +2880,7 @@ export class AnalyticsController {
30682880

30692881
await this.analyticsService.deleteData(
30702882
body.pid,
3071-
body.filters || '[]',
2883+
normalizeFiltersToV1Json(body.filters, 'traffic'),
30722884
body.from || null,
30732885
body.to || null,
30742886
body.types,

backend/apps/cloud/src/analytics/analytics.module.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ import {
2121
SESSION_REPLAY_EXPORT_QUEUE,
2222
} from './session-replay-export.service'
2323
import { SessionReplayExportProcessor } from './session-replay-export.processor'
24+
import { AnalyticsV2Service } from './v2/analytics-v2.service'
25+
import { SeoV2Service } from './v2/seo-v2.service'
26+
import { TrafficV2Controller } from './v2/controllers/traffic-v2.controller'
27+
import { PerformanceV2Controller } from './v2/controllers/performance-v2.controller'
28+
import { CaptchaV2Controller } from './v2/controllers/captcha-v2.controller'
29+
import { ErrorsV2Controller } from './v2/controllers/errors-v2.controller'
30+
import { SessionsV2Controller } from './v2/controllers/sessions-v2.controller'
31+
import { ProfilesV2Controller } from './v2/controllers/profiles-v2.controller'
32+
import { ProjectV2Controller } from './v2/controllers/project-v2.controller'
33+
import { SeoV2Controller } from './v2/controllers/seo-v2.controller'
2434

2535
@Module({
2636
imports: [
@@ -34,6 +44,8 @@ import { SessionReplayExportProcessor } from './session-replay-export.processor'
3444
],
3545
providers: [
3646
AnalyticsService,
47+
AnalyticsV2Service,
48+
SeoV2Service,
3749
BotDetectionService,
3850
SaltService,
3951
SessionReplayS3Service,
@@ -44,6 +56,16 @@ import { SessionReplayExportProcessor } from './session-replay-export.processor'
4456
PublicProjectCacheInterceptor,
4557
],
4658
exports: [AnalyticsService, BotDetectionService, SaltService],
47-
controllers: [AnalyticsController],
59+
controllers: [
60+
AnalyticsController,
61+
TrafficV2Controller,
62+
PerformanceV2Controller,
63+
CaptchaV2Controller,
64+
ErrorsV2Controller,
65+
SessionsV2Controller,
66+
ProfilesV2Controller,
67+
ProjectV2Controller,
68+
SeoV2Controller,
69+
],
4870
})
4971
export class AnalyticsModule {}

0 commit comments

Comments
 (0)