@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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/index.d.ts +158 -0
  3. package/dist/api/index.js +317 -0
  4. package/dist/api/index.js.map +1 -0
  5. package/dist/catalog.d.ts +14 -0
  6. package/dist/catalog.js +209 -0
  7. package/dist/catalog.js.map +1 -0
  8. package/dist/components/index.d.ts +172 -0
  9. package/dist/components/index.js +1258 -0
  10. package/dist/components/index.js.map +1 -0
  11. package/dist/engine.d.ts +20 -0
  12. package/dist/engine.js +350 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +353 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/providers/database.d.ts +79 -0
  18. package/dist/providers/database.js +533 -0
  19. package/dist/providers/database.js.map +1 -0
  20. package/dist/providers/derived.d.ts +45 -0
  21. package/dist/providers/derived.js +32 -0
  22. package/dist/providers/derived.js.map +1 -0
  23. package/dist/providers/ga4.d.ts +43 -0
  24. package/dist/providers/ga4.js +220 -0
  25. package/dist/providers/ga4.js.map +1 -0
  26. package/dist/providers/index.d.ts +8 -0
  27. package/dist/providers/index.js +1239 -0
  28. package/dist/providers/index.js.map +1 -0
  29. package/dist/providers/mux.d.ts +103 -0
  30. package/dist/providers/mux.js +241 -0
  31. package/dist/providers/mux.js.map +1 -0
  32. package/dist/providers/survey.d.ts +102 -0
  33. package/dist/providers/survey.js +233 -0
  34. package/dist/providers/survey.js.map +1 -0
  35. package/dist/types.d.ts +303 -0
  36. package/dist/types.js +1 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +101 -0
  39. package/src/api/catalog-handler.ts +321 -0
  40. package/src/api/index.ts +4 -0
  41. package/src/api/token-handler.ts +71 -0
  42. package/src/catalog.ts +223 -0
  43. package/src/components/country-chart.tsx +114 -0
  44. package/src/components/index.ts +5 -0
  45. package/src/components/omnibus-dashboard.tsx +1460 -0
  46. package/src/components/revenue-chart.tsx +251 -0
  47. package/src/components/use-chart-colors.ts +75 -0
  48. package/src/engine.ts +201 -0
  49. package/src/index.ts +7 -0
  50. package/src/providers/database.ts +795 -0
  51. package/src/providers/derived.ts +79 -0
  52. package/src/providers/ga4.ts +173 -0
  53. package/src/providers/index.ts +44 -0
  54. package/src/providers/mux.ts +438 -0
  55. package/src/providers/survey.ts +487 -0
  56. 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'