Skip to content

Commit 735edff

Browse files
committed
sdk stats
1 parent 1f6829c commit 735edff

3 files changed

Lines changed: 226 additions & 4 deletions

File tree

scripts/fetch-ga.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ export async function fetchGoogleAnalyticsData() {
8282
'/couchbase-edge-server/',
8383
]
8484

85+
// SDK paths for comparison
86+
const sdkPaths = [
87+
'/dotnet-sdk/',
88+
'/efcore-provider/',
89+
'/c-sdk/',
90+
'/cxx-sdk/',
91+
'/go-sdk/',
92+
'/java-sdk/',
93+
'/quarkus-extension/',
94+
'/kotlin-sdk/',
95+
'/nodejs-sdk/',
96+
'/php-sdk/',
97+
'/python-sdk/',
98+
'/ruby-sdk/',
99+
'/rust-sdk/',
100+
'/scala-sdk/',
101+
]
102+
85103
const topPagesByPath = await Promise.all(
86104
documentationPaths.map(async (path) => {
87105
try {
@@ -354,6 +372,93 @@ export async function fetchGoogleAnalyticsData() {
354372

355373
console.log(` ✅ Fetched metrics for ${pathMetrics.length} documentation paths`)
356374

375+
// Fetch metrics for each SDK path
376+
const sdkMetrics = await Promise.all(
377+
sdkPaths.map(async (path) => {
378+
try {
379+
// Fetch page views for this SDK path
380+
const [pathViewsResponse] = await analyticsDataClient.runReport({
381+
property: `properties/${propertyId}`,
382+
dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
383+
dimensions: [{ name: 'pagePath' }],
384+
metrics: [{ name: 'screenPageViews' }],
385+
dimensionFilter: {
386+
filter: {
387+
fieldName: 'pagePath',
388+
stringFilter: { matchType: 'BEGINS_WITH', value: path },
389+
},
390+
},
391+
})
392+
393+
// Fetch session metrics for this SDK path
394+
const [pathSessionResponse] = await analyticsDataClient.runReport({
395+
property: `properties/${propertyId}`,
396+
dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
397+
metrics: [
398+
{ name: 'sessions' },
399+
{ name: 'bounceRate' },
400+
{ name: 'averageSessionDuration' },
401+
],
402+
dimensionFilter: {
403+
filter: {
404+
fieldName: 'pagePath',
405+
stringFilter: { matchType: 'BEGINS_WITH', value: path },
406+
},
407+
},
408+
})
409+
410+
// Fetch traffic sources for this SDK path
411+
const [pathTrafficResponse] = await analyticsDataClient.runReport({
412+
property: `properties/${propertyId}`,
413+
dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
414+
dimensions: [{ name: 'sessionDefaultChannelGroup' }],
415+
metrics: [{ name: 'sessions' }],
416+
dimensionFilter: {
417+
filter: {
418+
fieldName: 'pagePath',
419+
stringFilter: { matchType: 'BEGINS_WITH', value: path },
420+
},
421+
},
422+
})
423+
424+
const totalViews = pathViewsResponse.rows?.reduce((sum, row) => sum + parseInt(row.metricValues[0].value), 0) || 0
425+
const bounceRate = pathSessionResponse.rows?.[0]?.metricValues[1]?.value || '0'
426+
const avgSessionDuration = pathSessionResponse.rows?.[0]?.metricValues[2]?.value || '0'
427+
428+
// Calculate traffic balance
429+
const trafficData = pathTrafficResponse.rows || []
430+
const totalSessions = trafficData.reduce((sum, row) => sum + parseInt(row.metricValues[0].value), 0)
431+
const directSessions = trafficData.find(row => row.dimensionValues[0].value === 'Direct')?.metricValues[0]?.value || 0
432+
const searchSessions = trafficData.find(row => row.dimensionValues[0].value === 'Organic Search')?.metricValues[0]?.value || 0
433+
434+
const directPercentage = totalSessions > 0 ? Math.round((directSessions / totalSessions) * 100) : 0
435+
const searchPercentage = totalSessions > 0 ? Math.round((searchSessions / totalSessions) * 100) : 0
436+
437+
return {
438+
displayPath: path,
439+
totalViews,
440+
bounceRate: parseFloat(bounceRate).toFixed(1),
441+
avgSessionDuration: formatDuration(parseFloat(avgSessionDuration)),
442+
trafficBalance: {
443+
direct: directPercentage,
444+
search: searchPercentage,
445+
},
446+
}
447+
} catch (error) {
448+
console.warn(` ⚠️ Failed to fetch metrics for ${path}:`, error.message)
449+
return {
450+
displayPath: path,
451+
totalViews: 0,
452+
bounceRate: 0,
453+
avgSessionDuration: '0:00',
454+
trafficBalance: { direct: 0, search: 0 },
455+
}
456+
}
457+
})
458+
)
459+
460+
console.log(` ✅ Fetched metrics for ${sdkMetrics.length} SDK paths`)
461+
357462
// Process responses into dashboard format
358463
const daily = pageViewsResponse.rows.map(row => ({
359464
date: row.dimensionValues[0].value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'),
@@ -410,6 +515,7 @@ export async function fetchGoogleAnalyticsData() {
410515
{ device: 'Tablet', percentage: 6 },
411516
], // Requires additional query
412517
pathComparison: pathMetrics,
518+
sdkComparison: sdkMetrics,
413519
}
414520
} catch (error) {
415521
console.error('❌ Error fetching GA data:', error.message)

src/App.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import {
1313

1414
// Components
1515
import { MetricCard } from './components/MetricCard'
16-
import { SearchTermsTable } from './components/charts/SearchTermsTable'
1716
import { PageViewsChart } from './components/charts/PageViewsChart'
1817
import { TopPagesTable } from './components/charts/TopPagesTable'
1918
import { JiraLabelsChart } from './components/charts/JiraLabelsChart'
2019
import { VelocityChart } from './components/charts/VelocityChart'
2120
import { RecentIssuesTable } from './components/charts/RecentIssuesTable'
2221
import { TrafficSourcesChart } from './components/charts/TrafficSourcesChart'
2322
import { PathComparisonTable } from './components/charts/PathComparisonTable'
23+
import { SDKComparisonTable } from './components/charts/SDKComparisonTable'
2424
import { AIAssistant } from './components/AIAssistant'
2525
import { LLMInsights } from './components/LLMInsights'
2626
import { PasswordProtection } from './components/PasswordProtection'
@@ -313,9 +313,10 @@ function App() {
313313
</div>
314314
</div>
315315

316-
<PathComparisonTable data={analytics.pathComparison || []} />
317-
318-
<SearchTermsTable data={analytics.searchTerms || []} />
316+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
317+
<PathComparisonTable data={analytics.pathComparison || []} />
318+
<SDKComparisonTable data={analytics.sdkComparison || []} />
319+
</div>
319320
</div>
320321
)}
321322

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React from 'react'
2+
import { ChartCard } from '../ChartCard'
3+
4+
/**
5+
* Table comparing metrics across different SDK documentation paths
6+
*/
7+
export function SDKComparisonTable({ data }) {
8+
if (!data || data.length === 0) {
9+
return (
10+
<ChartCard title="SDK Comparison" subtitle="Compare metrics across SDK documentation">
11+
<p className="text-sm text-slate-500 text-center py-8">No SDK data available</p>
12+
</ChartCard>
13+
)
14+
}
15+
16+
const formatSDKName = (path) => {
17+
// Extract SDK name from path and format it nicely
18+
const sdkMap = {
19+
'dotnet-sdk': '.NET SDK',
20+
'efcore-provider': 'EF Core Provider',
21+
'c-sdk': 'C SDK',
22+
'cxx-sdk': 'C++ SDK',
23+
'go-sdk': 'Go SDK',
24+
'java-sdk': 'Java SDK',
25+
'quarkus-extension': 'Quarkus Extension',
26+
'kotlin-sdk': 'Kotlin SDK',
27+
'nodejs-sdk': 'Node.js SDK',
28+
'php-sdk': 'PHP SDK',
29+
'python-sdk': 'Python SDK',
30+
'ruby-sdk': 'Ruby SDK',
31+
'rust-sdk': 'Rust SDK',
32+
'scala-sdk': 'Scala SDK'
33+
}
34+
35+
const pathKey = path.replace(/^\/|\/$/g, '') // Remove leading/trailing slashes
36+
return sdkMap[pathKey] || formatPath(path)
37+
}
38+
39+
const formatPath = (path) => {
40+
return path.replace(/\//g, '').replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
41+
}
42+
43+
return (
44+
<ChartCard
45+
title="SDK Comparison"
46+
subtitle="Metrics for each SDK documentation (Last 30 Days)"
47+
>
48+
<div className="overflow-x-auto">
49+
<table className="w-full">
50+
<thead>
51+
<tr className="text-left text-sm text-slate-500 border-b border-slate-200">
52+
<th className="pb-3 font-medium">SDK</th>
53+
<th className="pb-3 font-medium text-right">Total Views</th>
54+
<th className="pb-3 font-medium text-right">Bounce Rate</th>
55+
<th className="pb-3 font-medium text-right">Avg. Session</th>
56+
<th className="pb-3 font-medium text-right">Traffic Balance</th>
57+
</tr>
58+
</thead>
59+
<tbody>
60+
{data.map((path, index) => (
61+
<tr
62+
key={index}
63+
className="border-b border-slate-50 last:border-0 hover:bg-slate-50 transition-colors"
64+
>
65+
<td className="py-3">
66+
<div className="font-medium text-slate-900">
67+
{formatSDKName(path.displayPath)}
68+
</div>
69+
<div className="text-xs text-slate-500 mt-0.5">
70+
{path.displayPath}
71+
</div>
72+
</td>
73+
<td className="py-3 text-right">
74+
<span className="font-medium text-slate-900">
75+
{path.totalViews.toLocaleString()}
76+
</span>
77+
</td>
78+
<td className="py-3 text-right">
79+
<span className="font-medium text-slate-900">
80+
{path.bounceRate}%
81+
</span>
82+
</td>
83+
<td className="py-3 text-right">
84+
<span className="font-medium text-slate-900">
85+
{path.avgSessionDuration}
86+
</span>
87+
</td>
88+
<td className="py-3 text-right">
89+
<div className="flex items-center justify-end gap-2">
90+
<div className="flex items-center gap-1">
91+
<div className="w-2 h-2 rounded-full bg-blue-500"></div>
92+
<span className="text-xs text-slate-600">
93+
{path.trafficBalance.direct}%
94+
</span>
95+
</div>
96+
<span className="text-slate-300">|</span>
97+
<div className="flex items-center gap-1">
98+
<div className="w-2 h-2 rounded-full bg-green-500"></div>
99+
<span className="text-xs text-slate-600">
100+
{path.trafficBalance.search}%
101+
</span>
102+
</div>
103+
</div>
104+
<div className="text-xs text-slate-500 mt-1">
105+
Direct / Search
106+
</div>
107+
</td>
108+
</tr>
109+
))}
110+
</tbody>
111+
</table>
112+
</div>
113+
</ChartCard>
114+
)
115+
}

0 commit comments

Comments
 (0)