@coursebuilder/analytics 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/api/index.d.ts +158 -0
- package/dist/api/index.js +317 -0
- package/dist/api/index.js.map +1 -0
- package/dist/catalog.d.ts +14 -0
- package/dist/catalog.js +209 -0
- package/dist/catalog.js.map +1 -0
- package/dist/components/index.d.ts +172 -0
- package/dist/components/index.js +1258 -0
- package/dist/components/index.js.map +1 -0
- package/dist/engine.d.ts +20 -0
- package/dist/engine.js +350 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/database.d.ts +79 -0
- package/dist/providers/database.js +533 -0
- package/dist/providers/database.js.map +1 -0
- package/dist/providers/derived.d.ts +45 -0
- package/dist/providers/derived.js +32 -0
- package/dist/providers/derived.js.map +1 -0
- package/dist/providers/ga4.d.ts +43 -0
- package/dist/providers/ga4.js +220 -0
- package/dist/providers/ga4.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +1239 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/mux.d.ts +103 -0
- package/dist/providers/mux.js +241 -0
- package/dist/providers/mux.js.map +1 -0
- package/dist/providers/survey.d.ts +102 -0
- package/dist/providers/survey.js +233 -0
- package/dist/providers/survey.js.map +1 -0
- package/dist/types.d.ts +303 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +101 -0
- package/src/api/catalog-handler.ts +321 -0
- package/src/api/index.ts +4 -0
- package/src/api/token-handler.ts +71 -0
- package/src/catalog.ts +223 -0
- package/src/components/country-chart.tsx +114 -0
- package/src/components/index.ts +5 -0
- package/src/components/omnibus-dashboard.tsx +1460 -0
- package/src/components/revenue-chart.tsx +251 -0
- package/src/components/use-chart-colors.ts +75 -0
- package/src/engine.ts +201 -0
- package/src/index.ts +7 -0
- package/src/providers/database.ts +795 -0
- package/src/providers/derived.ts +79 -0
- package/src/providers/ga4.ts +173 -0
- package/src/providers/index.ts +44 -0
- package/src/providers/mux.ts +438 -0
- package/src/providers/survey.ts +487 -0
- package/src/types.ts +333 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { GA4TimeRange } from './ga4'
|
|
2
|
+
|
|
3
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type AnalyticsRange = '24h' | '7d' | '30d' | '90d' | 'all'
|
|
6
|
+
|
|
7
|
+
export interface TrafficRevenueCorrelation {
|
|
8
|
+
traffic: {
|
|
9
|
+
date: string
|
|
10
|
+
sessions: number
|
|
11
|
+
users: number
|
|
12
|
+
pageviews: number
|
|
13
|
+
}[]
|
|
14
|
+
revenue: {
|
|
15
|
+
date: string
|
|
16
|
+
revenue: number
|
|
17
|
+
count: number
|
|
18
|
+
}[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DerivedProviderDeps {
|
|
22
|
+
database: {
|
|
23
|
+
getRevenueByDay: (range: AnalyticsRange) => Promise<
|
|
24
|
+
{
|
|
25
|
+
date: string
|
|
26
|
+
revenue: number
|
|
27
|
+
count: number
|
|
28
|
+
}[]
|
|
29
|
+
>
|
|
30
|
+
}
|
|
31
|
+
ga4: {
|
|
32
|
+
getSessionsByDay: (range: GA4TimeRange) => Promise<
|
|
33
|
+
{
|
|
34
|
+
date: string
|
|
35
|
+
sessions: number
|
|
36
|
+
users: number
|
|
37
|
+
pageviews: number
|
|
38
|
+
}[]
|
|
39
|
+
>
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a derived analytics provider that combines data from multiple
|
|
47
|
+
* sources (database + GA4) to compute correlation metrics.
|
|
48
|
+
*
|
|
49
|
+
* @param deps - Provider dependencies (database and ga4 providers)
|
|
50
|
+
*/
|
|
51
|
+
export function createDerivedProvider(deps: DerivedProviderDeps) {
|
|
52
|
+
const { database, ga4 } = deps
|
|
53
|
+
|
|
54
|
+
function toGA4Range(range: AnalyticsRange): GA4TimeRange {
|
|
55
|
+
if (range === 'all') return '90d'
|
|
56
|
+
return range
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Correlates GA4 traffic sessions by day with revenue by day,
|
|
61
|
+
* enabling side-by-side analysis of traffic and revenue trends.
|
|
62
|
+
*/
|
|
63
|
+
async function getTrafficRevenueCorrelation(
|
|
64
|
+
range: AnalyticsRange,
|
|
65
|
+
): Promise<TrafficRevenueCorrelation> {
|
|
66
|
+
const [traffic, revenue] = await Promise.all([
|
|
67
|
+
ga4.getSessionsByDay(toGA4Range(range)),
|
|
68
|
+
database.getRevenueByDay(range),
|
|
69
|
+
])
|
|
70
|
+
|
|
71
|
+
return { traffic, revenue }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
getTrafficRevenueCorrelation,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type DerivedAnalyticsProvider = ReturnType<typeof createDerivedProvider>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { BetaAnalyticsDataClient } from '@google-analytics/data'
|
|
2
|
+
|
|
3
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type GA4TimeRange = '24h' | '7d' | '30d' | '90d'
|
|
6
|
+
|
|
7
|
+
export interface GA4ProviderConfig {
|
|
8
|
+
propertyId: string
|
|
9
|
+
clientEmail: string
|
|
10
|
+
privateKey: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a GA4 analytics provider with injected credentials.
|
|
17
|
+
* Lazily initializes the BetaAnalyticsDataClient on first use.
|
|
18
|
+
*
|
|
19
|
+
* @param config - GA4 credentials and property configuration
|
|
20
|
+
*/
|
|
21
|
+
export function createGA4Provider(config: GA4ProviderConfig) {
|
|
22
|
+
const { propertyId, clientEmail, privateKey } = config
|
|
23
|
+
|
|
24
|
+
let _client: BetaAnalyticsDataClient | null = null
|
|
25
|
+
|
|
26
|
+
function getClient(): BetaAnalyticsDataClient {
|
|
27
|
+
if (!_client) {
|
|
28
|
+
_client = new BetaAnalyticsDataClient({
|
|
29
|
+
credentials: {
|
|
30
|
+
client_email: clientEmail,
|
|
31
|
+
private_key: privateKey.replace(/\\n/g, '\n'),
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
return _client
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function rangeToDateRange(range: GA4TimeRange) {
|
|
39
|
+
const map: Record<GA4TimeRange, string> = {
|
|
40
|
+
'24h': '1daysAgo',
|
|
41
|
+
'7d': '7daysAgo',
|
|
42
|
+
'30d': '30daysAgo',
|
|
43
|
+
'90d': '90daysAgo',
|
|
44
|
+
}
|
|
45
|
+
return { startDate: map[range], endDate: 'today' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Reports ──────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
async function getTrafficOverview(range: GA4TimeRange = '30d') {
|
|
51
|
+
const client = getClient()
|
|
52
|
+
|
|
53
|
+
const [response] = await client.runReport({
|
|
54
|
+
property: `properties/${propertyId}`,
|
|
55
|
+
dateRanges: [rangeToDateRange(range)],
|
|
56
|
+
metrics: [
|
|
57
|
+
{ name: 'sessions' },
|
|
58
|
+
{ name: 'totalUsers' },
|
|
59
|
+
{ name: 'newUsers' },
|
|
60
|
+
{ name: 'screenPageViews' },
|
|
61
|
+
{ name: 'averageSessionDuration' },
|
|
62
|
+
{ name: 'bounceRate' },
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const row = response?.rows?.[0]
|
|
67
|
+
if (!row) {
|
|
68
|
+
return {
|
|
69
|
+
sessions: 0,
|
|
70
|
+
totalUsers: 0,
|
|
71
|
+
newUsers: 0,
|
|
72
|
+
pageviews: 0,
|
|
73
|
+
avgSessionDuration: 0,
|
|
74
|
+
bounceRate: 0,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
sessions: Number(row.metricValues?.[0]?.value ?? 0),
|
|
80
|
+
totalUsers: Number(row.metricValues?.[1]?.value ?? 0),
|
|
81
|
+
newUsers: Number(row.metricValues?.[2]?.value ?? 0),
|
|
82
|
+
pageviews: Number(row.metricValues?.[3]?.value ?? 0),
|
|
83
|
+
avgSessionDuration: Number(row.metricValues?.[4]?.value ?? 0),
|
|
84
|
+
bounceRate: Number(row.metricValues?.[5]?.value ?? 0),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function getTopPages(range: GA4TimeRange = '30d', limit = 20) {
|
|
89
|
+
const client = getClient()
|
|
90
|
+
|
|
91
|
+
const [response] = await client.runReport({
|
|
92
|
+
property: `properties/${propertyId}`,
|
|
93
|
+
dateRanges: [rangeToDateRange(range)],
|
|
94
|
+
dimensions: [{ name: 'pagePath' }],
|
|
95
|
+
metrics: [
|
|
96
|
+
{ name: 'screenPageViews' },
|
|
97
|
+
{ name: 'totalUsers' },
|
|
98
|
+
{ name: 'averageSessionDuration' },
|
|
99
|
+
],
|
|
100
|
+
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
|
101
|
+
limit,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
response?.rows?.map((row) => ({
|
|
106
|
+
path: row.dimensionValues?.[0]?.value ?? '',
|
|
107
|
+
pageviews: Number(row.metricValues?.[0]?.value ?? 0),
|
|
108
|
+
users: Number(row.metricValues?.[1]?.value ?? 0),
|
|
109
|
+
avgDuration: Number(row.metricValues?.[2]?.value ?? 0),
|
|
110
|
+
})) ?? []
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function getTrafficSources(range: GA4TimeRange = '30d', limit = 15) {
|
|
115
|
+
const client = getClient()
|
|
116
|
+
|
|
117
|
+
const [response] = await client.runReport({
|
|
118
|
+
property: `properties/${propertyId}`,
|
|
119
|
+
dateRanges: [rangeToDateRange(range)],
|
|
120
|
+
dimensions: [{ name: 'sessionSource' }, { name: 'sessionMedium' }],
|
|
121
|
+
metrics: [{ name: 'sessions' }, { name: 'totalUsers' }],
|
|
122
|
+
orderBys: [{ metric: { metricName: 'sessions' }, desc: true }],
|
|
123
|
+
limit,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
response?.rows?.map((row) => ({
|
|
128
|
+
source: row.dimensionValues?.[0]?.value ?? '(direct)',
|
|
129
|
+
medium: row.dimensionValues?.[1]?.value ?? '(none)',
|
|
130
|
+
sessions: Number(row.metricValues?.[0]?.value ?? 0),
|
|
131
|
+
users: Number(row.metricValues?.[1]?.value ?? 0),
|
|
132
|
+
})) ?? []
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function getSessionsByDay(range: GA4TimeRange = '30d') {
|
|
137
|
+
const client = getClient()
|
|
138
|
+
|
|
139
|
+
const [response] = await client.runReport({
|
|
140
|
+
property: `properties/${propertyId}`,
|
|
141
|
+
dateRanges: [rangeToDateRange(range)],
|
|
142
|
+
dimensions: [{ name: 'date' }],
|
|
143
|
+
metrics: [
|
|
144
|
+
{ name: 'sessions' },
|
|
145
|
+
{ name: 'totalUsers' },
|
|
146
|
+
{ name: 'screenPageViews' },
|
|
147
|
+
],
|
|
148
|
+
orderBys: [{ dimension: { dimensionName: 'date' }, desc: false }],
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
response?.rows?.map((row) => {
|
|
153
|
+
const raw = row.dimensionValues?.[0]?.value ?? ''
|
|
154
|
+
const date = `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`
|
|
155
|
+
return {
|
|
156
|
+
date,
|
|
157
|
+
sessions: Number(row.metricValues?.[0]?.value ?? 0),
|
|
158
|
+
users: Number(row.metricValues?.[1]?.value ?? 0),
|
|
159
|
+
pageviews: Number(row.metricValues?.[2]?.value ?? 0),
|
|
160
|
+
}
|
|
161
|
+
}) ?? []
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
getTrafficOverview,
|
|
167
|
+
getTopPages,
|
|
168
|
+
getTrafficSources,
|
|
169
|
+
getSessionsByDay,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type GA4AnalyticsProvider = ReturnType<typeof createGA4Provider>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Provider implementations are exported from their individual sub-paths:
|
|
2
|
+
// @coursebuilder/analytics/providers/database
|
|
3
|
+
// @coursebuilder/analytics/providers/ga4
|
|
4
|
+
// @coursebuilder/analytics/providers/derived
|
|
5
|
+
// @coursebuilder/analytics/providers/mux
|
|
6
|
+
//
|
|
7
|
+
// This barrel re-exports the provider interface types and all factories.
|
|
8
|
+
export type { AnalyticsProvider, ProviderMap } from '../engine'
|
|
9
|
+
export {
|
|
10
|
+
createDatabaseProvider,
|
|
11
|
+
type DatabaseAnalyticsProvider,
|
|
12
|
+
type DatabaseAnalyticsSchema,
|
|
13
|
+
type AnalyticsTimeRange,
|
|
14
|
+
type AttributionTrail,
|
|
15
|
+
type AttributionTrailEvent,
|
|
16
|
+
} from './database'
|
|
17
|
+
export {
|
|
18
|
+
createGA4Provider,
|
|
19
|
+
type GA4AnalyticsProvider,
|
|
20
|
+
type GA4ProviderConfig,
|
|
21
|
+
type GA4TimeRange,
|
|
22
|
+
} from './ga4'
|
|
23
|
+
export {
|
|
24
|
+
createDerivedProvider,
|
|
25
|
+
type DerivedAnalyticsProvider,
|
|
26
|
+
type DerivedProviderDeps,
|
|
27
|
+
type AnalyticsRange,
|
|
28
|
+
type TrafficRevenueCorrelation,
|
|
29
|
+
} from './derived'
|
|
30
|
+
export {
|
|
31
|
+
createMuxProvider,
|
|
32
|
+
type MuxAnalyticsProvider,
|
|
33
|
+
type MuxProviderConfig,
|
|
34
|
+
type MuxDbDeps,
|
|
35
|
+
type VideoDashboardData,
|
|
36
|
+
type VideoDetailBreakdowns,
|
|
37
|
+
type TimeRange,
|
|
38
|
+
type VideoTableRange,
|
|
39
|
+
type MuxOverallResponse,
|
|
40
|
+
type MuxTimeseriesResponse,
|
|
41
|
+
type MuxBreakdownItem,
|
|
42
|
+
type MuxBreakdownResponse,
|
|
43
|
+
} from './mux'
|
|
44
|
+
export { createSurveyProvider, type SurveyAnalyticsProvider } from './survey'
|