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