diff --git a/features/platform-analytics/backend-api/src/modules/ab-testing/ab-testing.service.ts b/features/platform-analytics/backend-api/src/modules/ab-testing/ab-testing.service.ts index 676f18fd5..d6544b827 100644 --- a/features/platform-analytics/backend-api/src/modules/ab-testing/ab-testing.service.ts +++ b/features/platform-analytics/backend-api/src/modules/ab-testing/ab-testing.service.ts @@ -79,11 +79,11 @@ export class ABTestingService { 'test.name', 'test.description', 'test.status', - 'test.goal_metric', - 'test.start_date', - 'test.end_date', - 'test.created_at', - 'test.updated_at', + 'test.goalMetric', + 'test.startDate', + 'test.endDate', + 'test.createdAt', + 'test.updatedAt', ]) .addSelect('COUNT(DISTINCT assignment.id)::int', 'totalImpressions') .addSelect('COUNT(DISTINCT CASE WHEN assignment.converted THEN assignment.id END)::int', 'totalConversions') diff --git a/features/platform-analytics/backend-api/src/modules/analytics-gateway/analytics-gateway.controller.ts b/features/platform-analytics/backend-api/src/modules/analytics-gateway/analytics-gateway.controller.ts index d443f4858..178445ec6 100644 --- a/features/platform-analytics/backend-api/src/modules/analytics-gateway/analytics-gateway.controller.ts +++ b/features/platform-analytics/backend-api/src/modules/analytics-gateway/analytics-gateway.controller.ts @@ -375,25 +375,40 @@ export class AnalyticsGatewayController { */ @Get('segments') async getSegmentsData(@Query() query: DateRangeQueryDto & { dimension?: string; metric?: string }) { - const segments = await this.analyticsClient.getSegments().catch((err) => { - this.logger.warn(`getSegments failed: ${err.message}`); - return []; - }); + const dimension = (query.dimension ?? 'device') as string; + const metric = (query.metric ?? 'sessions') as string; - const dimension = (query.dimension ?? 'source') as 'device' | 'browser' | 'os' | 'country' | 'source' | 'medium' | 'campaign'; - const metric = (query.metric ?? 'sessions') as 'sessions' | 'users' | 'pageViews' | 'conversions' | 'bounceRate'; - const total = segments.length; + let rawItems: Array<{ name: string; sessions: number; percentage: number }> = []; + + if (dimension === 'device') { + const data = await this.analyticsClient.getDevices(query).catch(() => []); + rawItems = data.map((d) => ({ name: d.deviceType, sessions: d.sessions, percentage: d.percentage * 100 })); + } else if (dimension === 'browser') { + const data = await this.analyticsClient.getBrowsers(query).catch(() => []); + rawItems = data.map((d) => ({ name: d.browser, sessions: d.sessions, percentage: d.percentage * 100 })); + } else if (dimension === 'country') { + const data = await this.analyticsClient.getGeography(query).catch(() => []); + rawItems = data.map((d) => ({ name: d.location, sessions: d.sessions, percentage: d.percentage * 100 })); + } else if (dimension === 'source') { + const data = await this.analyticsClient.getChannels(query).catch(() => []); + rawItems = data.map((d) => ({ name: d.channel, sessions: d.sessions, percentage: 0 })); + const total = rawItems.reduce((s, r) => s + r.sessions, 0); + rawItems = rawItems.map((r) => ({ ...r, percentage: total > 0 ? (r.sessions / total) * 100 : 0 })); + } else { + const data = await this.analyticsClient.getChannels(query).catch(() => []); + rawItems = data.map((d) => ({ name: d.channel, sessions: d.sessions, percentage: 0 })); + } return { dimension, metric, - segments: segments.map((s, i) => ({ - name: s.name, - value: total > 0 ? Math.round(1000 / total) : 0, - percentage: total > 0 ? Math.round(100 / total) : 0, + segments: rawItems.map((item) => ({ + name: item.name, + value: item.sessions, + percentage: Math.round(item.percentage * 10) / 10, trend: 0, })), - total, + total: rawItems.reduce((s, r) => s + r.sessions, 0), }; } @@ -687,9 +702,11 @@ export class AnalyticsGatewayController { stages, totalUsers, overallConversionRate: Math.round(overallConversionRate * 100) / 100, - biggestDropOff: stages.length >= 2 - ? stages.slice(1).reduce((max, s) => s.dropOffRate > max.dropOffRate ? s : max, stages[1]) - : { stage: '', count: 0, conversionRate: 0, dropOffRate: 0 }, + biggestDropOff: (() => { + if (stages.length < 2) return { fromStage: '', toStage: '', dropOffRate: 0 }; + const maxIdx = stages.slice(1).reduce((maxI, s, i) => s.dropOffRate > stages.slice(1)[maxI].dropOffRate ? i : maxI, 0) + 1; + return { fromStage: stages[maxIdx - 1].stage, toStage: stages[maxIdx].stage, dropOffRate: stages[maxIdx].dropOffRate }; + })(), dateRange: { startDate: start.toISOString(), endDate: end.toISOString() }, }; } diff --git a/features/platform-analytics/frontend-platform/src/pages/SegmentsPage.tsx b/features/platform-analytics/frontend-platform/src/pages/SegmentsPage.tsx index 0eb352631..ee68c8e01 100644 --- a/features/platform-analytics/frontend-platform/src/pages/SegmentsPage.tsx +++ b/features/platform-analytics/frontend-platform/src/pages/SegmentsPage.tsx @@ -180,7 +180,7 @@ const SegmentsPage = () => { sortable: true, render: (row) => { const trend = row.trend > 0 ? 'up' : row.trend < 0 ? 'down' : 'flat'; - const Icon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus; + const Icon = trend === 'up' ? TrendingUpIcon : trend === 'down' ? TrendingDownIcon : MinusIcon; return ( diff --git a/features/platform-analytics/funnels.png b/features/platform-analytics/funnels.png new file mode 100644 index 000000000..de8b8add7 Binary files /dev/null and b/features/platform-analytics/funnels.png differ diff --git a/features/platform-analytics/segments.png b/features/platform-analytics/segments.png new file mode 100644 index 000000000..9117339f1 Binary files /dev/null and b/features/platform-analytics/segments.png differ