vite-plugin-smart-prefetch 0.3.4 → 0.3.6
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/dist/index.cjs +257 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +256 -138
- package/dist/index.js.map +1 -1
- package/dist/react 2/index.cjs +700 -0
- package/dist/react 2/index.cjs.map +1 -0
- package/dist/react 2/index.d.cts +157 -0
- package/dist/react 2/index.d.ts +157 -0
- package/dist/react 2/index.js +660 -0
- package/dist/react 2/index.js.map +1 -0
- package/dist/runtime 2/index.cjs +582 -0
- package/dist/runtime 2/index.cjs.map +1 -0
- package/dist/runtime 2/index.d.cts +192 -0
- package/dist/runtime 2/index.d.ts +192 -0
- package/dist/runtime 2/index.js +549 -0
- package/dist/runtime 2/index.js.map +1 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -218,4 +218,42 @@ interface ViteManifest {
|
|
|
218
218
|
|
|
219
219
|
declare function smartPrefetch(options?: PluginOptions): Plugin;
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
/**
|
|
222
|
+
* BigQuery Analytics Connector
|
|
223
|
+
* Queries GA4 event data from BigQuery to extract real navigation transitions
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
declare class BigQueryAnalyticsConnector {
|
|
227
|
+
private bigquery;
|
|
228
|
+
private projectId;
|
|
229
|
+
private datasetId;
|
|
230
|
+
private location;
|
|
231
|
+
private debug;
|
|
232
|
+
constructor(projectId: string, datasetId: string, location?: string, debug?: boolean);
|
|
233
|
+
/**
|
|
234
|
+
* Fetch real navigation transitions from BigQuery GA4 export
|
|
235
|
+
* Queries the events table for page_view events with previous_page_path parameter
|
|
236
|
+
*/
|
|
237
|
+
fetchNavigationSequences(config?: DataRangeConfig): Promise<NavigationData[]>;
|
|
238
|
+
/**
|
|
239
|
+
* Normalize route paths
|
|
240
|
+
* Converts dynamic segments to parameters and standardizes format
|
|
241
|
+
*/
|
|
242
|
+
private normalizeRoute;
|
|
243
|
+
/**
|
|
244
|
+
* Save raw and processed data for inspection
|
|
245
|
+
* Helps identify issues in data transformation pipeline
|
|
246
|
+
*/
|
|
247
|
+
private saveDataForInspection;
|
|
248
|
+
/**
|
|
249
|
+
* Fetch navigation data WITH optional segment information
|
|
250
|
+
* Only includes segment/grouping field when explicitly requested
|
|
251
|
+
* @param config - Data range configuration with optional segmentField
|
|
252
|
+
* @returns Navigation data, optionally with segment field included
|
|
253
|
+
*/
|
|
254
|
+
fetchNavigationWithSegments(config?: DataRangeConfig & {
|
|
255
|
+
segmentField?: string;
|
|
256
|
+
}): Promise<any[]>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export { type AdvancedConfig, type AnalyticsConfig, BigQueryAnalyticsConnector, type BigQueryCredentials, type CacheConfig, type DataRangeConfig, type ManualRules, type ModelConfig, type NavigationData, type PluginOptions, type PrefetchConfig, type PrefetchModel, type PrefetchStrategy, type PrefetchTarget, type ViteManifest, smartPrefetch };
|
package/dist/index.d.ts
CHANGED
|
@@ -218,4 +218,42 @@ interface ViteManifest {
|
|
|
218
218
|
|
|
219
219
|
declare function smartPrefetch(options?: PluginOptions): Plugin;
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
/**
|
|
222
|
+
* BigQuery Analytics Connector
|
|
223
|
+
* Queries GA4 event data from BigQuery to extract real navigation transitions
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
declare class BigQueryAnalyticsConnector {
|
|
227
|
+
private bigquery;
|
|
228
|
+
private projectId;
|
|
229
|
+
private datasetId;
|
|
230
|
+
private location;
|
|
231
|
+
private debug;
|
|
232
|
+
constructor(projectId: string, datasetId: string, location?: string, debug?: boolean);
|
|
233
|
+
/**
|
|
234
|
+
* Fetch real navigation transitions from BigQuery GA4 export
|
|
235
|
+
* Queries the events table for page_view events with previous_page_path parameter
|
|
236
|
+
*/
|
|
237
|
+
fetchNavigationSequences(config?: DataRangeConfig): Promise<NavigationData[]>;
|
|
238
|
+
/**
|
|
239
|
+
* Normalize route paths
|
|
240
|
+
* Converts dynamic segments to parameters and standardizes format
|
|
241
|
+
*/
|
|
242
|
+
private normalizeRoute;
|
|
243
|
+
/**
|
|
244
|
+
* Save raw and processed data for inspection
|
|
245
|
+
* Helps identify issues in data transformation pipeline
|
|
246
|
+
*/
|
|
247
|
+
private saveDataForInspection;
|
|
248
|
+
/**
|
|
249
|
+
* Fetch navigation data WITH optional segment information
|
|
250
|
+
* Only includes segment/grouping field when explicitly requested
|
|
251
|
+
* @param config - Data range configuration with optional segmentField
|
|
252
|
+
* @returns Navigation data, optionally with segment field included
|
|
253
|
+
*/
|
|
254
|
+
fetchNavigationWithSegments(config?: DataRangeConfig & {
|
|
255
|
+
segmentField?: string;
|
|
256
|
+
}): Promise<any[]>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export { type AdvancedConfig, type AnalyticsConfig, BigQueryAnalyticsConnector, type BigQueryCredentials, type CacheConfig, type DataRangeConfig, type ManualRules, type ModelConfig, type NavigationData, type PluginOptions, type PrefetchConfig, type PrefetchModel, type PrefetchStrategy, type PrefetchTarget, type ViteManifest, smartPrefetch };
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var BigQueryAnalyticsConnector = class {
|
|
|
6
6
|
constructor(projectId, datasetId, location = "asia-south1", debug = false) {
|
|
7
7
|
this.projectId = projectId;
|
|
8
8
|
this.datasetId = datasetId;
|
|
9
|
-
this.location = location;
|
|
9
|
+
this.location = location && location !== "asia" ? location : "asia-south1";
|
|
10
10
|
this.debug = debug;
|
|
11
11
|
this.bigquery = new BigQuery({
|
|
12
12
|
projectId
|
|
@@ -15,7 +15,8 @@ var BigQueryAnalyticsConnector = class {
|
|
|
15
15
|
console.log("\u2705 BigQuery Analytics connector initialized");
|
|
16
16
|
console.log(` Project ID: ${projectId}`);
|
|
17
17
|
console.log(` Dataset ID: ${datasetId}`);
|
|
18
|
-
console.log(` Location: ${location}`);
|
|
18
|
+
console.log(` Location: ${this.location}`);
|
|
19
|
+
console.log(` Querying daily events tables: ${projectId}.${datasetId}.events_*`);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
@@ -25,20 +26,27 @@ var BigQueryAnalyticsConnector = class {
|
|
|
25
26
|
async fetchNavigationSequences(config = {}) {
|
|
26
27
|
const { days = 30 } = config;
|
|
27
28
|
const minSessions = 1;
|
|
28
|
-
console.log(
|
|
29
|
-
|
|
29
|
+
console.log(`
|
|
30
|
+
${"\u2550".repeat(60)}`);
|
|
31
|
+
console.log(`\u{1F4CA} STAGE 1: PREPARING BIGQUERY QUERY`);
|
|
32
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
33
|
+
console.log(` Project ID: ${this.projectId}`);
|
|
34
|
+
console.log(` Dataset ID: ${this.datasetId}`);
|
|
35
|
+
console.log(` Location: ${this.location}`);
|
|
30
36
|
console.log(` Date range: Last ${days} days`);
|
|
37
|
+
console.log(` Daily tables pattern: events_*`);
|
|
38
|
+
console.log(` Event type filter: page_view`);
|
|
39
|
+
console.log(` Expected fields: user_pseudo_id, ga_session_id, page_path`);
|
|
31
40
|
try {
|
|
32
41
|
const query = `
|
|
33
42
|
WITH page_sessions AS (
|
|
34
43
|
SELECT
|
|
35
|
-
|
|
44
|
+
user_pseudo_id,
|
|
36
45
|
(SELECT value.int_value FROM UNNEST(event_params) WHERE KEY = 'ga_session_id' LIMIT 1) as session_id,
|
|
37
|
-
(SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = 'page_location' LIMIT 1) as page_location,
|
|
38
46
|
(SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = 'page_path' LIMIT 1) as page_path,
|
|
39
47
|
event_timestamp,
|
|
40
48
|
ROW_NUMBER() OVER (
|
|
41
|
-
PARTITION BY
|
|
49
|
+
PARTITION BY user_pseudo_id, (SELECT value.int_value FROM UNNEST(event_params) WHERE KEY = 'ga_session_id' LIMIT 1)
|
|
42
50
|
ORDER BY event_timestamp
|
|
43
51
|
) as page_sequence
|
|
44
52
|
FROM \`${this.projectId}.${this.datasetId}.events_*\`
|
|
@@ -52,7 +60,7 @@ var BigQueryAnalyticsConnector = class {
|
|
|
52
60
|
SELECT
|
|
53
61
|
COALESCE(curr.page_path, '(direct)') as from_page,
|
|
54
62
|
LEAD(curr.page_path) OVER (
|
|
55
|
-
PARTITION BY curr.
|
|
63
|
+
PARTITION BY curr.user_pseudo_id, curr.session_id
|
|
56
64
|
ORDER BY curr.page_sequence
|
|
57
65
|
) as to_page
|
|
58
66
|
FROM page_sessions curr
|
|
@@ -64,52 +72,132 @@ var BigQueryAnalyticsConnector = class {
|
|
|
64
72
|
COUNT(*) as transition_count
|
|
65
73
|
FROM transitions
|
|
66
74
|
WHERE to_page IS NOT NULL
|
|
67
|
-
GROUP BY
|
|
75
|
+
GROUP BY previous_page_path, page_path
|
|
68
76
|
ORDER BY transition_count DESC
|
|
69
77
|
LIMIT 10000
|
|
70
78
|
`;
|
|
71
79
|
if (this.debug) {
|
|
72
|
-
console.log(
|
|
80
|
+
console.log(`
|
|
81
|
+
\u{1F4E4} Full BigQuery Query:`);
|
|
82
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
73
83
|
console.log(query);
|
|
84
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
74
85
|
}
|
|
86
|
+
console.log(`
|
|
87
|
+
${"\u2550".repeat(60)}`);
|
|
88
|
+
console.log(`\u{1F4CA} STAGE 2: EXECUTING QUERY`);
|
|
89
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
90
|
+
console.log(` \u23F3 Querying BigQuery...`);
|
|
75
91
|
const [rows] = await this.bigquery.query({
|
|
76
92
|
query,
|
|
77
93
|
location: this.location
|
|
78
94
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
console.log(` \u2705 Query executed successfully`);
|
|
96
|
+
console.log(` \u{1F4C8} Total transitions returned: ${rows.length}`);
|
|
97
|
+
if (rows.length === 0) {
|
|
98
|
+
console.log(`
|
|
99
|
+
${"\u2550".repeat(60)}`);
|
|
100
|
+
console.log(`\u26A0\uFE0F STAGE 2: NO DATA RETURNED!`);
|
|
101
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
102
|
+
console.log(` \u274C BigQuery returned 0 rows`);
|
|
103
|
+
console.log(`
|
|
104
|
+
Possible causes:`);
|
|
105
|
+
console.log(` 1. No event tables in date range (events_YYYYMMDD)`);
|
|
106
|
+
console.log(` 2. No page_view events in those tables`);
|
|
107
|
+
console.log(` 3. page_path parameter not being tracked`);
|
|
108
|
+
console.log(` 4. Wrong location (currently: ${this.location})`);
|
|
109
|
+
console.log(` 5. Wrong dataset name: ${this.datasetId}`);
|
|
110
|
+
console.log(`
|
|
111
|
+
Troubleshooting:`);
|
|
112
|
+
console.log(` \u2022 Check BigQuery console for available event tables`);
|
|
113
|
+
console.log(` \u2022 Verify GA4 property is exporting to BigQuery`);
|
|
114
|
+
console.log(` \u2022 Confirm page_path is being captured in GA4`);
|
|
115
|
+
console.log(`${"\u2550".repeat(60)}
|
|
116
|
+
`);
|
|
117
|
+
return [];
|
|
82
118
|
}
|
|
119
|
+
console.log(`
|
|
120
|
+
${"\u2550".repeat(60)}`);
|
|
121
|
+
console.log(`\u{1F4CA} STAGE 3: PROCESSING RAW DATA`);
|
|
122
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
123
|
+
console.log(` Processing ${rows.length} raw transitions...`);
|
|
83
124
|
const navigationData = [];
|
|
125
|
+
let processedCount = 0;
|
|
126
|
+
let filteredCount = 0;
|
|
127
|
+
let emptyPathCount = 0;
|
|
128
|
+
let lowCountCount = 0;
|
|
84
129
|
const rawData = rows.map((row) => ({
|
|
85
130
|
previous_page_path: row.previous_page_path,
|
|
86
131
|
page_path: row.page_path,
|
|
87
132
|
transition_count: row.transition_count
|
|
88
133
|
}));
|
|
89
|
-
|
|
134
|
+
console.log(`
|
|
135
|
+
${"\u2500".repeat(56)}`);
|
|
136
|
+
console.log(` Processing each transition row...`);
|
|
137
|
+
console.log(` ${"\u2500".repeat(56)}`);
|
|
138
|
+
rows.forEach((row, index) => {
|
|
90
139
|
const previousPage = row.previous_page_path || "(direct)";
|
|
91
140
|
const currentPage = row.page_path || "";
|
|
92
141
|
const transitionCount = parseInt(row.transition_count || "0");
|
|
142
|
+
if (this.debug && index < 5) {
|
|
143
|
+
console.log(`
|
|
144
|
+
Row ${index + 1}:`);
|
|
145
|
+
console.log(` Raw: "${previousPage}" \u2192 "${currentPage}" (count: ${transitionCount})`);
|
|
146
|
+
}
|
|
93
147
|
const normalizedPrevious = previousPage === "(direct)" || previousPage === "(not set)" ? "(direct)" : this.normalizeRoute(previousPage);
|
|
94
148
|
const normalizedCurrent = this.normalizeRoute(currentPage);
|
|
149
|
+
if (this.debug && index < 5) {
|
|
150
|
+
console.log(` Normalized: "${normalizedPrevious}" \u2192 "${normalizedCurrent}"`);
|
|
151
|
+
}
|
|
95
152
|
if (normalizedCurrent && transitionCount >= minSessions) {
|
|
96
153
|
navigationData.push({
|
|
97
154
|
from: normalizedPrevious,
|
|
98
155
|
to: normalizedCurrent,
|
|
99
156
|
count: transitionCount
|
|
100
157
|
});
|
|
158
|
+
processedCount++;
|
|
159
|
+
if (this.debug && index < 5) {
|
|
160
|
+
console.log(` \u2705 ACCEPTED`);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
filteredCount++;
|
|
164
|
+
if (!normalizedCurrent) {
|
|
165
|
+
emptyPathCount++;
|
|
166
|
+
if (this.debug && index < 5) {
|
|
167
|
+
console.log(` \u274C FILTERED: normalized to empty path (likely build artifact)`);
|
|
168
|
+
}
|
|
169
|
+
} else if (transitionCount < minSessions) {
|
|
170
|
+
lowCountCount++;
|
|
171
|
+
if (this.debug && index < 5) {
|
|
172
|
+
console.log(` \u274C FILTERED: transition count ${transitionCount} < minSessions ${minSessions}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
101
175
|
}
|
|
102
176
|
});
|
|
177
|
+
console.log(`
|
|
178
|
+
${"\u2500".repeat(56)}`);
|
|
179
|
+
console.log(` Processing Summary:`);
|
|
180
|
+
console.log(` \u2705 Accepted: ${processedCount}`);
|
|
181
|
+
console.log(` \u274C Filtered out: ${filteredCount}`);
|
|
182
|
+
console.log(` \u2022 Empty paths: ${emptyPathCount}`);
|
|
183
|
+
console.log(` \u2022 Low transition count: ${lowCountCount}`);
|
|
103
184
|
this.saveDataForInspection(rawData, navigationData);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
185
|
+
console.log(`
|
|
186
|
+
${"\u2550".repeat(60)}`);
|
|
187
|
+
console.log(`\u{1F4CA} STAGE 4: FINAL RESULTS`);
|
|
188
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
189
|
+
console.log(` Total valid transitions: ${navigationData.length}`);
|
|
190
|
+
if (navigationData.length === 0) {
|
|
191
|
+
console.log(`
|
|
192
|
+
\u26A0\uFE0F WARNING: All transitions were filtered out!`);
|
|
193
|
+
console.log(` Check the logs above to see why rows were rejected.`);
|
|
194
|
+
console.log(` Raw data saved to: .bigquery-raw-data/raw-bigquery-response.json`);
|
|
195
|
+
} else {
|
|
196
|
+
console.log(`
|
|
108
197
|
Top 5 transitions:`);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
198
|
+
navigationData.slice(0, 5).forEach((nav, i) => {
|
|
199
|
+
console.log(` ${i + 1}. ${nav.from} \u2192 ${nav.to} (${nav.count})`);
|
|
200
|
+
});
|
|
113
201
|
const uniqueRoutes = /* @__PURE__ */ new Set();
|
|
114
202
|
navigationData.forEach((nav) => {
|
|
115
203
|
if (nav.from !== "(direct)") uniqueRoutes.add(nav.from);
|
|
@@ -119,85 +207,45 @@ var BigQueryAnalyticsConnector = class {
|
|
|
119
207
|
\u{1F4CA} Unique routes: ${uniqueRoutes.size}`);
|
|
120
208
|
console.log(` Routes: ${Array.from(uniqueRoutes).sort().join(", ")}`);
|
|
121
209
|
console.log(`
|
|
122
|
-
\u{1F4C1}
|
|
210
|
+
\u{1F4C1} Data inspection files created:`);
|
|
211
|
+
console.log(` \u2022 .bigquery-raw-data/raw-bigquery-response.json`);
|
|
212
|
+
console.log(` \u2022 .bigquery-raw-data/processed-navigation-data.json`);
|
|
213
|
+
console.log(` \u2022 .bigquery-raw-data/data-transformation-summary.json`);
|
|
123
214
|
}
|
|
215
|
+
console.log(`${"\u2550".repeat(60)}
|
|
216
|
+
`);
|
|
124
217
|
return navigationData;
|
|
125
218
|
} catch (error) {
|
|
126
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
219
|
+
const errorMessage = error instanceof Error ? error.message : String(error) || "Unknown error";
|
|
220
|
+
console.log(`
|
|
221
|
+
${"\u2550".repeat(60)}`);
|
|
222
|
+
console.log(`\u274C ERROR: QUERY EXECUTION FAILED`);
|
|
223
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
127
224
|
if (errorMessage.includes("bigquery.jobs.create") || errorMessage.includes("PERMISSION_DENIED")) {
|
|
128
|
-
console.error(
|
|
129
|
-
console.error(
|
|
130
|
-
console.error(
|
|
225
|
+
console.error(` \u274C Permission Denied`);
|
|
226
|
+
console.error(` Service account needs BigQuery Job User role in project: ${this.projectId}`);
|
|
227
|
+
console.error(` Error: ${errorMessage}`);
|
|
228
|
+
} else if (errorMessage.includes("was not found")) {
|
|
229
|
+
console.error(` \u274C Dataset not found`);
|
|
230
|
+
console.error(` Cannot find dataset: ${this.projectId}.${this.datasetId}`);
|
|
231
|
+
console.error(` Try a different location or check dataset name`);
|
|
232
|
+
console.error(` Current location: ${this.location}`);
|
|
233
|
+
console.error(` Error: ${errorMessage}`);
|
|
131
234
|
} else {
|
|
132
|
-
|
|
235
|
+
const errorType = error instanceof Error ? error.constructor.name : "Unknown";
|
|
236
|
+
console.error(` Error type: ${errorType}`);
|
|
237
|
+
console.error(` Message: ${errorMessage}`);
|
|
133
238
|
}
|
|
239
|
+
console.log(`${"\u2550".repeat(60)}
|
|
240
|
+
`);
|
|
134
241
|
throw new Error(
|
|
135
242
|
`BigQuery Analytics error: ${errorMessage}`
|
|
136
243
|
);
|
|
137
244
|
}
|
|
138
245
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Map friendly page names (with spaces/capitals) back to route paths
|
|
141
|
-
* GA4 may log display names instead of URL paths
|
|
142
|
-
* Examples: "Audit Logs" → "/audit-logs", "API Documentation" → "/api-docs", "Home" → "/"
|
|
143
|
-
*/
|
|
144
|
-
friendlyNameToRoute(name) {
|
|
145
|
-
const friendlyToRoute = {
|
|
146
|
-
// Exact matches (with spaces and capitals)
|
|
147
|
-
"API Documentation": "/api-docs",
|
|
148
|
-
"Audit Logs": "/audit-logs",
|
|
149
|
-
"/Home": "/",
|
|
150
|
-
"/home": "/",
|
|
151
|
-
"Home": "/",
|
|
152
|
-
"/Dashboard": "/dashboard",
|
|
153
|
-
"Dashboard": "/dashboard",
|
|
154
|
-
// PascalCase variants (all 27 routes)
|
|
155
|
-
"Profile": "/profile",
|
|
156
|
-
"Settings": "/settings",
|
|
157
|
-
"Preferences": "/preferences",
|
|
158
|
-
"Privacy": "/privacy",
|
|
159
|
-
"Security": "/security",
|
|
160
|
-
"Analytics": "/analytics",
|
|
161
|
-
"Reports": "/reports",
|
|
162
|
-
"Metrics": "/metrics",
|
|
163
|
-
"Projects": "/projects",
|
|
164
|
-
"Tasks": "/tasks",
|
|
165
|
-
"Teams": "/teams",
|
|
166
|
-
"Workspaces": "/workspaces",
|
|
167
|
-
"Workflows": "/workflows",
|
|
168
|
-
"Templates": "/templates",
|
|
169
|
-
"Logs": "/logs",
|
|
170
|
-
"AuditLogs": "/audit-logs",
|
|
171
|
-
"Integrations": "/integrations",
|
|
172
|
-
"ApiDocs": "/api-docs",
|
|
173
|
-
"Support": "/support",
|
|
174
|
-
"Help": "/help",
|
|
175
|
-
"Billing": "/billing",
|
|
176
|
-
"Plans": "/plans",
|
|
177
|
-
"Usage": "/usage",
|
|
178
|
-
"Permissions": "/permissions",
|
|
179
|
-
"Notifications": "/notifications"
|
|
180
|
-
};
|
|
181
|
-
if (friendlyToRoute[name]) {
|
|
182
|
-
return friendlyToRoute[name];
|
|
183
|
-
}
|
|
184
|
-
const lowerName = name.toLowerCase();
|
|
185
|
-
for (const [key, value] of Object.entries(friendlyToRoute)) {
|
|
186
|
-
if (key.toLowerCase() === lowerName) {
|
|
187
|
-
return value;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (name.startsWith("/")) {
|
|
191
|
-
if (name === "/") {
|
|
192
|
-
return "/";
|
|
193
|
-
}
|
|
194
|
-
return name.toLowerCase();
|
|
195
|
-
}
|
|
196
|
-
return "/" + lowerName;
|
|
197
|
-
}
|
|
198
246
|
/**
|
|
199
247
|
* Normalize route paths
|
|
200
|
-
* Converts dynamic segments to parameters and
|
|
248
|
+
* Converts dynamic segments to parameters and standardizes format
|
|
201
249
|
*/
|
|
202
250
|
normalizeRoute(path2) {
|
|
203
251
|
const buildArtifacts = [
|
|
@@ -211,7 +259,7 @@ var BigQueryAnalyticsConnector = class {
|
|
|
211
259
|
if (buildArtifacts.some((pattern) => pattern.test(path2))) {
|
|
212
260
|
return "";
|
|
213
261
|
}
|
|
214
|
-
let normalized =
|
|
262
|
+
let normalized = path2;
|
|
215
263
|
normalized = normalized.split("?")[0].split("#")[0];
|
|
216
264
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
217
265
|
normalized = normalized.slice(0, -1);
|
|
@@ -268,28 +316,49 @@ var BigQueryAnalyticsConnector = class {
|
|
|
268
316
|
}
|
|
269
317
|
}
|
|
270
318
|
/**
|
|
271
|
-
* Fetch navigation data WITH segment information
|
|
272
|
-
*
|
|
273
|
-
* @param config - Data range configuration
|
|
274
|
-
* @returns Navigation data with segment field included
|
|
319
|
+
* Fetch navigation data WITH optional segment information
|
|
320
|
+
* Only includes segment/grouping field when explicitly requested
|
|
321
|
+
* @param config - Data range configuration with optional segmentField
|
|
322
|
+
* @returns Navigation data, optionally with segment field included
|
|
275
323
|
*/
|
|
276
324
|
async fetchNavigationWithSegments(config = {}) {
|
|
277
|
-
const { days = 30 } = config;
|
|
278
|
-
|
|
279
|
-
|
|
325
|
+
const { days = 30, segmentField } = config;
|
|
326
|
+
if (!segmentField) {
|
|
327
|
+
console.log(`
|
|
328
|
+
${"\u2550".repeat(60)}`);
|
|
329
|
+
console.log(`\u{1F4CA} STAGE 1: PREPARING BIGQUERY QUERY (SEGMENTED)`);
|
|
330
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
331
|
+
console.log(` \u26A0\uFE0F No segment field specified`);
|
|
332
|
+
console.log(` Falling back to fetchNavigationSequences (base data only)`);
|
|
333
|
+
console.log(` To include segments, pass: { segmentField: 'field_name' }`);
|
|
334
|
+
console.log(`${"\u2550".repeat(60)}
|
|
335
|
+
`);
|
|
336
|
+
return this.fetchNavigationSequences(config);
|
|
337
|
+
}
|
|
338
|
+
console.log(`
|
|
339
|
+
${"\u2550".repeat(60)}`);
|
|
340
|
+
console.log(`\u{1F4CA} STAGE 1: PREPARING SEGMENTED BIGQUERY QUERY`);
|
|
341
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
342
|
+
console.log(` Project ID: ${this.projectId}`);
|
|
343
|
+
console.log(` Dataset ID: ${this.datasetId}`);
|
|
344
|
+
console.log(` Location: ${this.location}`);
|
|
280
345
|
console.log(` Date range: Last ${days} days`);
|
|
346
|
+
console.log(` Daily tables pattern: events_*`);
|
|
347
|
+
console.log(` Event type filter: page_view`);
|
|
348
|
+
console.log(` Segment field: ${segmentField}`);
|
|
349
|
+
console.log(` Expected fields: user_pseudo_id, ga_session_id, page_path, ${segmentField}`);
|
|
281
350
|
try {
|
|
351
|
+
const segmentParam = `(SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = '${segmentField}' LIMIT 1) as segment_value`;
|
|
282
352
|
const query = `
|
|
283
353
|
WITH page_sessions AS (
|
|
284
354
|
SELECT
|
|
285
|
-
|
|
355
|
+
user_pseudo_id,
|
|
286
356
|
(SELECT value.int_value FROM UNNEST(event_params) WHERE KEY = 'ga_session_id' LIMIT 1) as session_id,
|
|
287
357
|
(SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = 'page_path' LIMIT 1) as page_path,
|
|
288
|
-
|
|
289
|
-
(SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = 'user_segment' LIMIT 1) as user_segment,
|
|
358
|
+
${segmentParam},
|
|
290
359
|
event_timestamp,
|
|
291
360
|
ROW_NUMBER() OVER (
|
|
292
|
-
PARTITION BY
|
|
361
|
+
PARTITION BY user_pseudo_id, (SELECT value.int_value FROM UNNEST(event_params) WHERE KEY = 'ga_session_id' LIMIT 1)
|
|
293
362
|
ORDER BY event_timestamp
|
|
294
363
|
) as page_sequence
|
|
295
364
|
FROM \`${this.projectId}.${this.datasetId}.events_*\`
|
|
@@ -303,10 +372,10 @@ var BigQueryAnalyticsConnector = class {
|
|
|
303
372
|
SELECT
|
|
304
373
|
COALESCE(curr.page_path, '(direct)') as from_page,
|
|
305
374
|
LEAD(curr.page_path) OVER (
|
|
306
|
-
PARTITION BY curr.
|
|
375
|
+
PARTITION BY curr.user_pseudo_id, curr.session_id
|
|
307
376
|
ORDER BY curr.page_sequence
|
|
308
377
|
) as to_page,
|
|
309
|
-
COALESCE(curr.
|
|
378
|
+
COALESCE(curr.segment_value, 'unknown') as segment
|
|
310
379
|
FROM page_sessions curr
|
|
311
380
|
WHERE curr.page_sequence > 0
|
|
312
381
|
)
|
|
@@ -317,45 +386,104 @@ var BigQueryAnalyticsConnector = class {
|
|
|
317
386
|
COUNT(*) as transition_count
|
|
318
387
|
FROM transitions
|
|
319
388
|
WHERE to_page IS NOT NULL
|
|
320
|
-
GROUP BY
|
|
389
|
+
GROUP BY previous_page_path, page_path, segment
|
|
321
390
|
ORDER BY segment, transition_count DESC
|
|
322
391
|
LIMIT 50000
|
|
323
392
|
`;
|
|
324
393
|
if (this.debug) {
|
|
325
|
-
console.log(
|
|
394
|
+
console.log(`
|
|
395
|
+
\u{1F4E4} Full Query with segment field (${segmentField}):`);
|
|
396
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
326
397
|
console.log(query);
|
|
398
|
+
console.log(`${"\u2500".repeat(60)}`);
|
|
327
399
|
}
|
|
400
|
+
console.log(`
|
|
401
|
+
${"\u2550".repeat(60)}`);
|
|
402
|
+
console.log(`\u{1F4CA} STAGE 2: EXECUTING QUERY`);
|
|
403
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
404
|
+
console.log(` \u23F3 Querying BigQuery with segment field: ${segmentField}`);
|
|
328
405
|
const [rows] = await this.bigquery.query({
|
|
329
406
|
query,
|
|
330
407
|
location: this.location
|
|
331
408
|
});
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
409
|
+
console.log(` \u2705 Query executed successfully`);
|
|
410
|
+
console.log(` \u{1F4C8} Total transitions returned: ${rows.length}`);
|
|
411
|
+
if (rows.length === 0) {
|
|
412
|
+
console.log(`
|
|
413
|
+
${"\u2550".repeat(60)}`);
|
|
414
|
+
console.log(`\u26A0\uFE0F STAGE 2: NO DATA RETURNED!`);
|
|
415
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
416
|
+
console.log(` \u274C BigQuery returned 0 rows for segmented query`);
|
|
417
|
+
console.log(` Segment field: ${segmentField}`);
|
|
418
|
+
console.log(` Check if this field exists in your GA4 event_params`);
|
|
419
|
+
console.log(`${"\u2550".repeat(60)}
|
|
420
|
+
`);
|
|
421
|
+
return [];
|
|
335
422
|
}
|
|
423
|
+
console.log(`
|
|
424
|
+
${"\u2550".repeat(60)}`);
|
|
425
|
+
console.log(`\u{1F4CA} STAGE 3: PROCESSING SEGMENTED DATA`);
|
|
426
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
427
|
+
console.log(` Processing ${rows.length} raw transitions with segments...`);
|
|
336
428
|
const navigationData = [];
|
|
337
|
-
|
|
429
|
+
let processedCount = 0;
|
|
430
|
+
let filteredCount = 0;
|
|
431
|
+
const segmentCounts = /* @__PURE__ */ new Map();
|
|
432
|
+
console.log(`
|
|
433
|
+
${"\u2500".repeat(56)}`);
|
|
434
|
+
console.log(` Processing each transition row...`);
|
|
435
|
+
console.log(` ${"\u2500".repeat(56)}`);
|
|
436
|
+
rows.forEach((row, index) => {
|
|
338
437
|
const previousPage = row.previous_page_path || "(direct)";
|
|
339
438
|
const currentPage = row.page_path || "";
|
|
340
439
|
const transitionCount = parseInt(row.transition_count || "0");
|
|
341
440
|
const segment = row.segment || "unknown";
|
|
441
|
+
if (this.debug && index < 3) {
|
|
442
|
+
console.log(`
|
|
443
|
+
Row ${index + 1}:`);
|
|
444
|
+
console.log(` Raw: "${previousPage}" \u2192 "${currentPage}" | segment: "${segment}" (count: ${transitionCount})`);
|
|
445
|
+
}
|
|
342
446
|
const normalizedPrevious = previousPage === "(direct)" || previousPage === "(not set)" ? "(direct)" : this.normalizeRoute(previousPage);
|
|
343
447
|
const normalizedCurrent = this.normalizeRoute(currentPage);
|
|
448
|
+
if (this.debug && index < 3) {
|
|
449
|
+
console.log(` Normalized: "${normalizedPrevious}" \u2192 "${normalizedCurrent}" | segment: "${segment}"`);
|
|
450
|
+
}
|
|
344
451
|
if (normalizedCurrent && transitionCount >= 1) {
|
|
345
452
|
navigationData.push({
|
|
346
453
|
from: normalizedPrevious,
|
|
347
454
|
to: normalizedCurrent,
|
|
348
455
|
count: transitionCount,
|
|
349
456
|
segment
|
|
350
|
-
// Include segment/role field
|
|
351
457
|
});
|
|
458
|
+
processedCount++;
|
|
459
|
+
segmentCounts.set(segment, (segmentCounts.get(segment) || 0) + 1);
|
|
460
|
+
if (this.debug && index < 3) {
|
|
461
|
+
console.log(` \u2705 ACCEPTED`);
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
filteredCount++;
|
|
465
|
+
if (this.debug && index < 3) {
|
|
466
|
+
console.log(` \u274C FILTERED`);
|
|
467
|
+
}
|
|
352
468
|
}
|
|
353
469
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
470
|
+
console.log(`
|
|
471
|
+
${"\u2500".repeat(56)}`);
|
|
472
|
+
console.log(` Processing Summary:`);
|
|
473
|
+
console.log(` \u2705 Accepted: ${processedCount}`);
|
|
474
|
+
console.log(` \u274C Filtered out: ${filteredCount}`);
|
|
475
|
+
console.log(`
|
|
476
|
+
${"\u2550".repeat(60)}`);
|
|
477
|
+
console.log(`\u{1F4CA} STAGE 4: FINAL RESULTS (SEGMENTED)`);
|
|
478
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
479
|
+
console.log(` Total valid transitions: ${navigationData.length}`);
|
|
480
|
+
if (navigationData.length > 0) {
|
|
481
|
+
const uniqueSegments = Array.from(segmentCounts.keys()).sort();
|
|
357
482
|
console.log(`
|
|
358
|
-
Detected segments: ${
|
|
483
|
+
\u{1F4CA} Detected segments: ${uniqueSegments.length}`);
|
|
484
|
+
uniqueSegments.forEach((seg) => {
|
|
485
|
+
console.log(` \u2022 ${seg} (${segmentCounts.get(seg)} transitions)`);
|
|
486
|
+
});
|
|
359
487
|
const bySegment = /* @__PURE__ */ new Map();
|
|
360
488
|
navigationData.forEach((d) => {
|
|
361
489
|
if (!bySegment.has(d.segment)) {
|
|
@@ -365,42 +493,31 @@ var BigQueryAnalyticsConnector = class {
|
|
|
365
493
|
});
|
|
366
494
|
bySegment.forEach((transitions, segment) => {
|
|
367
495
|
console.log(`
|
|
368
|
-
Top 3 transitions for ${segment}:`);
|
|
496
|
+
Top 3 transitions for segment "${segment}":`);
|
|
369
497
|
transitions.sort((a, b) => b.count - a.count).slice(0, 3).forEach((nav, i) => {
|
|
370
|
-
console.log(`
|
|
498
|
+
console.log(` ${i + 1}. ${nav.from} \u2192 ${nav.to} (${nav.count})`);
|
|
371
499
|
});
|
|
372
500
|
});
|
|
501
|
+
} else {
|
|
502
|
+
console.log(` \u26A0\uFE0F WARNING: All transitions were filtered out!`);
|
|
373
503
|
}
|
|
504
|
+
console.log(`${"\u2550".repeat(60)}
|
|
505
|
+
`);
|
|
374
506
|
return navigationData;
|
|
375
507
|
} catch (error) {
|
|
376
508
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
509
|
+
console.log(`
|
|
510
|
+
${"\u2550".repeat(60)}`);
|
|
511
|
+
console.log(`\u274C ERROR: SEGMENTED QUERY EXECUTION FAILED`);
|
|
512
|
+
console.log(`${"\u2550".repeat(60)}`);
|
|
513
|
+
console.log(` Segment field: ${segmentField}`);
|
|
514
|
+
console.log(` Error: ${errorMessage}`);
|
|
515
|
+
console.log(`${"\u2550".repeat(60)}
|
|
516
|
+
`);
|
|
377
517
|
console.warn(`\u26A0\uFE0F Failed to fetch navigation data with segments: ${errorMessage}`);
|
|
378
518
|
return [];
|
|
379
519
|
}
|
|
380
520
|
}
|
|
381
|
-
/**
|
|
382
|
-
* Test connection to BigQuery
|
|
383
|
-
* Note: May fail if service account lacks bigquery.jobs.create permission
|
|
384
|
-
* but actual queries may still work. This is expected for viewer-only accounts.
|
|
385
|
-
*/
|
|
386
|
-
async testConnection() {
|
|
387
|
-
try {
|
|
388
|
-
const query = `SELECT 1 as test_value`;
|
|
389
|
-
await this.bigquery.query({
|
|
390
|
-
query,
|
|
391
|
-
location: this.location
|
|
392
|
-
});
|
|
393
|
-
if (this.debug) {
|
|
394
|
-
console.log("\u2705 BigQuery connection test successful");
|
|
395
|
-
}
|
|
396
|
-
return true;
|
|
397
|
-
} catch (error) {
|
|
398
|
-
if (this.debug) {
|
|
399
|
-
console.warn("\u26A0\uFE0F BigQuery connection test failed (may be expected for read-only accounts)");
|
|
400
|
-
}
|
|
401
|
-
return false;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
521
|
};
|
|
405
522
|
|
|
406
523
|
// src/plugin/model/guessjs-ml-trainer.ts
|
|
@@ -1818,6 +1935,7 @@ function generateDashboard(config) {
|
|
|
1818
1935
|
</html>`;
|
|
1819
1936
|
}
|
|
1820
1937
|
export {
|
|
1938
|
+
BigQueryAnalyticsConnector,
|
|
1821
1939
|
smartPrefetch
|
|
1822
1940
|
};
|
|
1823
1941
|
//# sourceMappingURL=index.js.map
|