@squadbase/vite-server 0.1.17-dev.24af54e → 0.1.17-dev.423ee34
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/cli/index.js +4873 -1073
- package/dist/connectors/airtable-oauth.js +78 -11
- package/dist/connectors/airtable.js +74 -11
- package/dist/connectors/amplitude.js +38 -11
- package/dist/connectors/anthropic.js +4 -2
- package/dist/connectors/asana.js +67 -13
- package/dist/connectors/attio.js +60 -16
- package/dist/connectors/aws-billing.js +38 -11
- package/dist/connectors/azure-sql.js +64 -13
- package/dist/connectors/backlog-api-key.js +70 -18
- package/dist/connectors/clickup.js +80 -13
- package/dist/connectors/cosmosdb.js +42 -15
- package/dist/connectors/customerio.js +39 -12
- package/dist/connectors/dbt.js +716 -28
- package/dist/connectors/freshdesk.js +112 -11
- package/dist/connectors/freshsales.js +38 -11
- package/dist/connectors/freshservice.js +38 -11
- package/dist/connectors/gamma.js +47 -20
- package/dist/connectors/gemini.js +4 -2
- package/dist/connectors/github.js +42 -15
- package/dist/connectors/gmail-oauth.js +38 -13
- package/dist/connectors/gmail.js +34 -7
- package/dist/connectors/google-ads.js +38 -11
- package/dist/connectors/google-analytics-oauth.js +182 -28
- package/dist/connectors/google-analytics.js +653 -104
- package/dist/connectors/google-audit-log.js +34 -7
- package/dist/connectors/google-calendar-oauth.js +91 -18
- package/dist/connectors/google-calendar.js +91 -14
- package/dist/connectors/google-docs.js +38 -13
- package/dist/connectors/google-drive.js +60 -13
- package/dist/connectors/google-search-console-oauth.js +156 -20
- package/dist/connectors/google-sheets.js +36 -9
- package/dist/connectors/google-slides.js +38 -13
- package/dist/connectors/grafana.js +75 -13
- package/dist/connectors/hubspot-oauth.js +69 -12
- package/dist/connectors/hubspot.js +55 -12
- package/dist/connectors/influxdb.js +38 -11
- package/dist/connectors/intercom-oauth.js +100 -15
- package/dist/connectors/intercom.js +42 -15
- package/dist/connectors/jdbc.js +36 -9
- package/dist/connectors/jira-api-key.js +98 -14
- package/dist/connectors/kintone-api-token.js +96 -21
- package/dist/connectors/kintone.js +84 -14
- package/dist/connectors/linear.js +84 -15
- package/dist/connectors/linkedin-ads.js +71 -17
- package/dist/connectors/mailchimp-oauth.js +36 -9
- package/dist/connectors/mailchimp.js +36 -9
- package/dist/connectors/meta-ads-oauth.js +63 -17
- package/dist/connectors/meta-ads.js +65 -17
- package/dist/connectors/mixpanel.js +38 -11
- package/dist/connectors/monday.js +39 -12
- package/dist/connectors/mongodb.js +38 -11
- package/dist/connectors/notion-oauth.js +88 -14
- package/dist/connectors/notion.js +90 -14
- package/dist/connectors/openai.js +4 -2
- package/dist/connectors/oracle.js +78 -20
- package/dist/connectors/outlook-oauth.js +48 -23
- package/dist/connectors/powerbi-oauth.js +321 -49
- package/dist/connectors/salesforce.js +72 -12
- package/dist/connectors/semrush.js +374 -52
- package/dist/connectors/sentry.js +66 -13
- package/dist/connectors/shopify-oauth.js +71 -13
- package/dist/connectors/shopify.js +38 -11
- package/dist/connectors/sqlserver.js +64 -13
- package/dist/connectors/stripe-api-key.js +96 -18
- package/dist/connectors/stripe-oauth.js +98 -22
- package/dist/connectors/supabase.js +55 -11
- package/dist/connectors/tableau.js +262 -92
- package/dist/connectors/tiktok-ads.js +67 -19
- package/dist/connectors/wix-store.js +38 -11
- package/dist/connectors/zendesk-oauth.js +83 -15
- package/dist/connectors/zendesk.js +42 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4902 -1077
- package/dist/main.js +4891 -1071
- package/dist/vite-plugin.js +4871 -1071
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ var init_parameter_definition = __esm({
|
|
|
16
16
|
type;
|
|
17
17
|
secret;
|
|
18
18
|
required;
|
|
19
|
+
isDeprecated;
|
|
19
20
|
constructor(config) {
|
|
20
21
|
this.slug = config.slug;
|
|
21
22
|
this.name = config.name;
|
|
@@ -24,6 +25,7 @@ var init_parameter_definition = __esm({
|
|
|
24
25
|
this.type = config.type;
|
|
25
26
|
this.secret = config.secret;
|
|
26
27
|
this.required = config.required;
|
|
28
|
+
this.isDeprecated = config.isDeprecated ?? false;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Get the parameter value from a ConnectorConnectionObject.
|
|
@@ -71,11 +73,12 @@ var parameters = {
|
|
|
71
73
|
propertyId: new ParameterDefinition({
|
|
72
74
|
slug: "property-id",
|
|
73
75
|
name: "Google Analytics Property ID",
|
|
74
|
-
description: "The Google Analytics 4 property ID (e.g., 123456789).",
|
|
76
|
+
description: "The Google Analytics 4 property ID (e.g., 123456789). Automatically set during the setup flow.",
|
|
75
77
|
envVarBaseKey: "GA_PROPERTY_ID",
|
|
76
78
|
type: "text",
|
|
77
79
|
secret: false,
|
|
78
|
-
required:
|
|
80
|
+
required: false,
|
|
81
|
+
isDeprecated: true
|
|
79
82
|
})
|
|
80
83
|
};
|
|
81
84
|
|
|
@@ -107,15 +110,10 @@ function buildJwt(clientEmail, privateKey, nowSec) {
|
|
|
107
110
|
}
|
|
108
111
|
function createClient(params) {
|
|
109
112
|
const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
110
|
-
const
|
|
111
|
-
if (!serviceAccountKeyJsonBase64
|
|
112
|
-
const required = [
|
|
113
|
-
parameters.serviceAccountKeyJsonBase64.slug,
|
|
114
|
-
parameters.propertyId.slug
|
|
115
|
-
];
|
|
116
|
-
const missing = required.filter((s) => !params[s]);
|
|
113
|
+
const defaultPropertyId = params["property-id"] ?? "";
|
|
114
|
+
if (!serviceAccountKeyJsonBase64) {
|
|
117
115
|
throw new Error(
|
|
118
|
-
`google-analytics: missing required parameters: ${
|
|
116
|
+
`google-analytics: missing required parameters: ${parameters.serviceAccountKeyJsonBase64.slug}`
|
|
119
117
|
);
|
|
120
118
|
}
|
|
121
119
|
let serviceAccountKey;
|
|
@@ -169,19 +167,23 @@ function createClient(params) {
|
|
|
169
167
|
return {
|
|
170
168
|
async request(path2, init) {
|
|
171
169
|
const accessToken = await getAccessToken2();
|
|
172
|
-
const
|
|
173
|
-
const url = `${BASE_URL.replace(/\/+$/, "")}/${resolvedPath.replace(/^\/+/, "")}`;
|
|
170
|
+
const url = `${BASE_URL.replace(/\/+$/, "")}/${path2.replace(/^\/+/, "")}`;
|
|
174
171
|
const headers = new Headers(init?.headers);
|
|
175
172
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
176
173
|
return fetch(url, { ...init, headers });
|
|
177
174
|
},
|
|
178
175
|
async runReport(request) {
|
|
176
|
+
const { propertyId: pid, ...body } = request;
|
|
177
|
+
const propertyId = pid || defaultPropertyId;
|
|
178
|
+
if (!propertyId) {
|
|
179
|
+
throw new Error("google-analytics: propertyId is required in runReport request");
|
|
180
|
+
}
|
|
179
181
|
const response = await this.request(
|
|
180
182
|
`properties/${propertyId}:runReport`,
|
|
181
183
|
{
|
|
182
184
|
method: "POST",
|
|
183
185
|
headers: { "Content-Type": "application/json" },
|
|
184
|
-
body: JSON.stringify(
|
|
186
|
+
body: JSON.stringify(body)
|
|
185
187
|
}
|
|
186
188
|
);
|
|
187
189
|
if (!response.ok) {
|
|
@@ -196,7 +198,11 @@ function createClient(params) {
|
|
|
196
198
|
rowCount: data.rowCount ?? 0
|
|
197
199
|
};
|
|
198
200
|
},
|
|
199
|
-
async getMetadata() {
|
|
201
|
+
async getMetadata(request) {
|
|
202
|
+
const propertyId = request.propertyId || defaultPropertyId;
|
|
203
|
+
if (!propertyId) {
|
|
204
|
+
throw new Error("google-analytics: propertyId is required in getMetadata request");
|
|
205
|
+
}
|
|
200
206
|
const response = await this.request(
|
|
201
207
|
`properties/${propertyId}/metadata`,
|
|
202
208
|
{ method: "GET" }
|
|
@@ -210,12 +216,17 @@ function createClient(params) {
|
|
|
210
216
|
return await response.json();
|
|
211
217
|
},
|
|
212
218
|
async runRealtimeReport(request) {
|
|
219
|
+
const { propertyId: pid, ...body } = request;
|
|
220
|
+
const propertyId = pid || defaultPropertyId;
|
|
221
|
+
if (!propertyId) {
|
|
222
|
+
throw new Error("google-analytics: propertyId is required in runRealtimeReport request");
|
|
223
|
+
}
|
|
213
224
|
const response = await this.request(
|
|
214
225
|
`properties/${propertyId}:runRealtimeReport`,
|
|
215
226
|
{
|
|
216
227
|
method: "POST",
|
|
217
228
|
headers: { "Content-Type": "application/json" },
|
|
218
|
-
body: JSON.stringify(
|
|
229
|
+
body: JSON.stringify(body)
|
|
219
230
|
}
|
|
220
231
|
);
|
|
221
232
|
if (!response.ok) {
|
|
@@ -339,7 +350,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
339
350
|
/**
|
|
340
351
|
* Create tools for connections that belong to this connector.
|
|
341
352
|
* Filters connections by connectorKey internally.
|
|
342
|
-
* Returns tools keyed as
|
|
353
|
+
* Returns tools keyed as `connector_${connectorKey}_${toolName}`.
|
|
343
354
|
*/
|
|
344
355
|
createTools(connections, config, opts) {
|
|
345
356
|
const myConnections = connections.filter(
|
|
@@ -349,7 +360,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
349
360
|
for (const t of Object.values(this.tools)) {
|
|
350
361
|
const tool = t.createTool(myConnections, config);
|
|
351
362
|
const originalToModelOutput = tool.toModelOutput;
|
|
352
|
-
result[
|
|
363
|
+
result[`connector_${this.connectorKey}_${t.name}`] = {
|
|
353
364
|
...tool,
|
|
354
365
|
toModelOutput: async (options) => {
|
|
355
366
|
if (!originalToModelOutput) {
|
|
@@ -405,19 +416,34 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
405
416
|
};
|
|
406
417
|
let state = flow.initialState();
|
|
407
418
|
let answerIdx = 0;
|
|
419
|
+
const pendingParameterUpdates = [];
|
|
408
420
|
for (const step of flow.steps) {
|
|
409
421
|
const ans = ctx.answers[answerIdx];
|
|
410
422
|
if (ans && ans.questionSlug === step.slug) {
|
|
411
423
|
state = step.applyAnswer(state, ans.answer);
|
|
424
|
+
if (step.toParameterUpdates) {
|
|
425
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
426
|
+
}
|
|
412
427
|
answerIdx += 1;
|
|
413
428
|
continue;
|
|
414
429
|
}
|
|
430
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
415
431
|
if (step.type === "text") {
|
|
432
|
+
if (step.fetchOptions) {
|
|
433
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
434
|
+
if (options2.length === 0) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
416
438
|
return {
|
|
417
439
|
type: "nextQuestion",
|
|
418
440
|
questionSlug: step.slug,
|
|
419
441
|
question: step.question[ctx.language],
|
|
420
|
-
questionType: "text"
|
|
442
|
+
questionType: "text",
|
|
443
|
+
allowFreeText: resolvedAllowFreeText,
|
|
444
|
+
...pendingParameterUpdates.length > 0 && {
|
|
445
|
+
parameterUpdates: pendingParameterUpdates
|
|
446
|
+
}
|
|
421
447
|
};
|
|
422
448
|
}
|
|
423
449
|
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
@@ -429,11 +455,21 @@ async function runSetupFlow(flow, params, ctx, config) {
|
|
|
429
455
|
questionSlug: step.slug,
|
|
430
456
|
question: step.question[ctx.language],
|
|
431
457
|
questionType: step.type,
|
|
432
|
-
options
|
|
458
|
+
options,
|
|
459
|
+
allowFreeText: resolvedAllowFreeText,
|
|
460
|
+
...pendingParameterUpdates.length > 0 && {
|
|
461
|
+
parameterUpdates: pendingParameterUpdates
|
|
462
|
+
}
|
|
433
463
|
};
|
|
434
464
|
}
|
|
435
465
|
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
436
|
-
return {
|
|
466
|
+
return {
|
|
467
|
+
type: "fulfilled",
|
|
468
|
+
dataInvestigationResult,
|
|
469
|
+
...pendingParameterUpdates.length > 0 && {
|
|
470
|
+
parameterUpdates: pendingParameterUpdates
|
|
471
|
+
}
|
|
472
|
+
};
|
|
437
473
|
}
|
|
438
474
|
async function resolveSetupSelection(params) {
|
|
439
475
|
const { selected, allSentinel, fetchAll, limit } = params;
|
|
@@ -454,10 +490,10 @@ var AUTH_TYPES = {
|
|
|
454
490
|
// ../connectors/src/connectors/google-analytics/setup.ts
|
|
455
491
|
var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
456
492
|
dataOverviewInstructions: {
|
|
457
|
-
en: `1. Call
|
|
458
|
-
2. Call
|
|
459
|
-
ja: `1.
|
|
460
|
-
2.
|
|
493
|
+
en: `1. Call connector_google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
|
|
494
|
+
2. Call connector_google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
|
|
495
|
+
ja: `1. connector_google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
|
|
496
|
+
2. connector_google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
|
|
461
497
|
}
|
|
462
498
|
});
|
|
463
499
|
|
|
@@ -465,6 +501,7 @@ var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
|
465
501
|
import * as crypto2 from "crypto";
|
|
466
502
|
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
467
503
|
var ADMIN_BASE_URL = "https://analyticsadmin.googleapis.com/v1beta";
|
|
504
|
+
var DATA_BASE_URL = "https://analyticsdata.googleapis.com/v1beta";
|
|
468
505
|
var SCOPE2 = "https://www.googleapis.com/auth/analytics.readonly";
|
|
469
506
|
function base64url2(input) {
|
|
470
507
|
const buf = typeof input === "string" ? Buffer.from(input) : input;
|
|
@@ -546,10 +583,35 @@ async function adminFetch(params, path2, init) {
|
|
|
546
583
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
547
584
|
return fetch(url, { ...init, headers });
|
|
548
585
|
}
|
|
586
|
+
async function dataFetch(params, path2, init) {
|
|
587
|
+
const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
588
|
+
if (!keyJsonBase64) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
"google-analytics: missing required parameter: service-account-key-json-base64"
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const serviceAccountKey = decodeServiceAccount(keyJsonBase64);
|
|
594
|
+
const accessToken = await getAccessToken(serviceAccountKey);
|
|
595
|
+
const url = `${DATA_BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
596
|
+
const headers = new Headers(init?.headers);
|
|
597
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
598
|
+
return fetch(url, { ...init, headers });
|
|
599
|
+
}
|
|
549
600
|
|
|
550
601
|
// ../connectors/src/connectors/google-analytics/setup-flow.ts
|
|
551
602
|
var ALL_PROPERTIES = "__ALL_PROPERTIES__";
|
|
552
603
|
var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
|
|
604
|
+
var MAX_PROPERTIES_TO_EXPLORE = 2;
|
|
605
|
+
var TOP_N_SOURCES = 8;
|
|
606
|
+
var TOP_N_COUNTRIES = 8;
|
|
607
|
+
var TOP_N_BROWSERS = 5;
|
|
608
|
+
var TOP_N_PAGES = 10;
|
|
609
|
+
var TOP_N_EVENTS = 10;
|
|
610
|
+
var STANDARD_METADATA_LIMIT = 25;
|
|
611
|
+
var CUSTOM_METADATA_LIMIT = 15;
|
|
612
|
+
var DESCRIPTION_MAX = 140;
|
|
613
|
+
var PAGE_PATH_MAX = 80;
|
|
614
|
+
var EVENT_NAME_MAX = 60;
|
|
553
615
|
async function listAccountSummaries(params) {
|
|
554
616
|
const summaries = [];
|
|
555
617
|
let pageToken;
|
|
@@ -572,9 +634,401 @@ function propertyIdFromResource(resource) {
|
|
|
572
634
|
return (resource ?? "").replace(/^properties\//, "");
|
|
573
635
|
}
|
|
574
636
|
async function getProperty(params, propertyId) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
637
|
+
try {
|
|
638
|
+
const res = await adminFetch(params, `/properties/${propertyId}`);
|
|
639
|
+
if (!res.ok) return null;
|
|
640
|
+
return await res.json();
|
|
641
|
+
} catch {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function getMetadata(params, propertyId) {
|
|
646
|
+
try {
|
|
647
|
+
const res = await dataFetch(
|
|
648
|
+
params,
|
|
649
|
+
`/properties/${propertyId}/metadata`
|
|
650
|
+
);
|
|
651
|
+
if (!res.ok) return null;
|
|
652
|
+
return await res.json();
|
|
653
|
+
} catch {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async function probePropertyAccess(params, propertyId) {
|
|
658
|
+
try {
|
|
659
|
+
const res = await dataFetch(
|
|
660
|
+
params,
|
|
661
|
+
`/properties/${propertyId}:runReport`,
|
|
662
|
+
{
|
|
663
|
+
method: "POST",
|
|
664
|
+
headers: { "Content-Type": "application/json" },
|
|
665
|
+
body: JSON.stringify({
|
|
666
|
+
dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
|
|
667
|
+
metrics: [{ name: "activeUsers" }],
|
|
668
|
+
limit: 1
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
if (res.ok) return { ok: true };
|
|
673
|
+
const body = await res.text().catch(() => res.statusText);
|
|
674
|
+
return { ok: false, status: res.status, body };
|
|
675
|
+
} catch (err) {
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
status: "network",
|
|
679
|
+
body: err instanceof Error ? err.message : String(err)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async function batchRunReports(params, propertyId, requests) {
|
|
684
|
+
try {
|
|
685
|
+
const res = await dataFetch(
|
|
686
|
+
params,
|
|
687
|
+
`/properties/${propertyId}:batchRunReports`,
|
|
688
|
+
{
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: { "Content-Type": "application/json" },
|
|
691
|
+
body: JSON.stringify({ requests })
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
if (!res.ok) return requests.map(() => ({}));
|
|
695
|
+
const data = await res.json();
|
|
696
|
+
const reports = data.reports ?? [];
|
|
697
|
+
return requests.map((_, i) => reports[i] ?? {});
|
|
698
|
+
} catch {
|
|
699
|
+
return requests.map(() => ({}));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
var LAST_30D = { startDate: "30daysAgo", endDate: "today" };
|
|
703
|
+
var PREV_30D = { startDate: "60daysAgo", endDate: "31daysAgo" };
|
|
704
|
+
var HEADLINE_METRICS = [
|
|
705
|
+
"activeUsers",
|
|
706
|
+
"newUsers",
|
|
707
|
+
"sessions",
|
|
708
|
+
"screenPageViews",
|
|
709
|
+
"engagementRate",
|
|
710
|
+
"averageSessionDuration",
|
|
711
|
+
"keyEvents",
|
|
712
|
+
"totalRevenue"
|
|
713
|
+
];
|
|
714
|
+
function buildHeadlineRequest() {
|
|
715
|
+
return {
|
|
716
|
+
dateRanges: [
|
|
717
|
+
{ ...LAST_30D, name: "current" },
|
|
718
|
+
{ ...PREV_30D, name: "previous" }
|
|
719
|
+
],
|
|
720
|
+
metrics: HEADLINE_METRICS.map((name) => ({ name }))
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function buildTopDimensionRequest(dimension, metric, limit) {
|
|
724
|
+
return {
|
|
725
|
+
dateRanges: [LAST_30D],
|
|
726
|
+
dimensions: [{ name: dimension }],
|
|
727
|
+
metrics: [{ name: metric }],
|
|
728
|
+
limit,
|
|
729
|
+
orderBys: [{ desc: true, metric: { metricName: metric } }]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function buildTopPagesRequest() {
|
|
733
|
+
return {
|
|
734
|
+
dateRanges: [LAST_30D],
|
|
735
|
+
dimensions: [{ name: "pagePath" }],
|
|
736
|
+
metrics: [
|
|
737
|
+
{ name: "screenPageViews" },
|
|
738
|
+
{ name: "activeUsers" },
|
|
739
|
+
{ name: "averageSessionDuration" }
|
|
740
|
+
],
|
|
741
|
+
limit: TOP_N_PAGES,
|
|
742
|
+
orderBys: [{ desc: true, metric: { metricName: "screenPageViews" } }]
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function buildTopEventsRequest() {
|
|
746
|
+
return {
|
|
747
|
+
dateRanges: [LAST_30D],
|
|
748
|
+
dimensions: [{ name: "eventName" }],
|
|
749
|
+
metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
|
|
750
|
+
limit: TOP_N_EVENTS,
|
|
751
|
+
orderBys: [{ desc: true, metric: { metricName: "eventCount" } }]
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function escapePipe(s) {
|
|
755
|
+
return s.replace(/\|/g, "\\|");
|
|
756
|
+
}
|
|
757
|
+
function truncate(s, max) {
|
|
758
|
+
if (!s) return "";
|
|
759
|
+
if (s.length <= max) return s;
|
|
760
|
+
return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
761
|
+
}
|
|
762
|
+
function formatNumber(raw) {
|
|
763
|
+
if (raw == null || raw === "") return "-";
|
|
764
|
+
const n = Number(raw);
|
|
765
|
+
if (!Number.isFinite(n)) return raw;
|
|
766
|
+
if (Number.isInteger(n)) return n.toLocaleString("en-US");
|
|
767
|
+
return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
768
|
+
}
|
|
769
|
+
function formatPercent(raw) {
|
|
770
|
+
if (raw == null || raw === "") return "-";
|
|
771
|
+
const n = Number(raw);
|
|
772
|
+
if (!Number.isFinite(n)) return raw;
|
|
773
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
774
|
+
}
|
|
775
|
+
function formatDuration(rawSeconds) {
|
|
776
|
+
if (rawSeconds == null || rawSeconds === "") return "-";
|
|
777
|
+
const s = Math.round(Number(rawSeconds));
|
|
778
|
+
if (!Number.isFinite(s)) return rawSeconds;
|
|
779
|
+
if (s < 60) return `${s}s`;
|
|
780
|
+
const mins = Math.floor(s / 60);
|
|
781
|
+
const secs = s % 60;
|
|
782
|
+
return `${mins}m ${secs}s`;
|
|
783
|
+
}
|
|
784
|
+
function deltaArrow(curr, prev) {
|
|
785
|
+
if (!Number.isFinite(prev) || prev === 0) return "";
|
|
786
|
+
const pct = (curr - prev) / prev * 100;
|
|
787
|
+
if (Math.abs(pct) < 0.5) return " (\u2248 flat)";
|
|
788
|
+
const sign = pct > 0 ? "\u2191" : "\u2193";
|
|
789
|
+
return ` (${sign} ${Math.abs(pct).toFixed(1)}% vs prior 30d)`;
|
|
790
|
+
}
|
|
791
|
+
function pivotComparisonReport(report) {
|
|
792
|
+
const out = /* @__PURE__ */ new Map();
|
|
793
|
+
const headers = report.metricHeaders ?? [];
|
|
794
|
+
const rows = report.rows ?? [];
|
|
795
|
+
for (let i = 0; i < headers.length; i++) {
|
|
796
|
+
const name = headers[i]?.name;
|
|
797
|
+
if (!name) continue;
|
|
798
|
+
out.set(name, {
|
|
799
|
+
current: Number(rows[0]?.metricValues?.[i]?.value ?? "0"),
|
|
800
|
+
previous: Number(rows[1]?.metricValues?.[i]?.value ?? "0")
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
return out;
|
|
804
|
+
}
|
|
805
|
+
function renderHeadline(sections, report) {
|
|
806
|
+
const m = pivotComparisonReport(report);
|
|
807
|
+
if (m.size === 0) return;
|
|
808
|
+
sections.push("#### Headline metrics (last 30 days)", "");
|
|
809
|
+
const lines = [];
|
|
810
|
+
const push = (label, key, formatter) => {
|
|
811
|
+
const cell = m.get(key);
|
|
812
|
+
if (!cell) return;
|
|
813
|
+
const formatted = formatter(String(cell.current));
|
|
814
|
+
const delta = deltaArrow(cell.current, cell.previous);
|
|
815
|
+
lines.push([label, `${formatted}${delta}`]);
|
|
816
|
+
};
|
|
817
|
+
push("Active users", "activeUsers", formatNumber);
|
|
818
|
+
push("New users", "newUsers", formatNumber);
|
|
819
|
+
push("Sessions", "sessions", formatNumber);
|
|
820
|
+
push("Page / screen views", "screenPageViews", formatNumber);
|
|
821
|
+
push("Engagement rate", "engagementRate", formatPercent);
|
|
822
|
+
push("Avg session duration", "averageSessionDuration", formatDuration);
|
|
823
|
+
push("Key events (conversions)", "keyEvents", formatNumber);
|
|
824
|
+
const rev = m.get("totalRevenue");
|
|
825
|
+
if (rev && rev.current > 0) {
|
|
826
|
+
lines.push([
|
|
827
|
+
"Total revenue",
|
|
828
|
+
`${formatNumber(String(rev.current))}${deltaArrow(rev.current, rev.previous)}`
|
|
829
|
+
]);
|
|
830
|
+
}
|
|
831
|
+
for (const [k, v] of lines) sections.push(`- **${k}**: ${v}`);
|
|
832
|
+
sections.push("");
|
|
833
|
+
}
|
|
834
|
+
function renderBreakdown(sections, title, blurb, report, topN, metricFormatter = formatNumber) {
|
|
835
|
+
const rows = report.rows ?? [];
|
|
836
|
+
if (rows.length === 0) return;
|
|
837
|
+
const dim = report.dimensionHeaders?.[0]?.name ?? "dimension";
|
|
838
|
+
const metricName = report.metricHeaders?.[0]?.name ?? "metric";
|
|
839
|
+
sections.push(`#### ${title}`, "", blurb, "");
|
|
840
|
+
for (const row of rows.slice(0, topN)) {
|
|
841
|
+
const label = row.dimensionValues?.[0]?.value || `(empty ${dim})`;
|
|
842
|
+
const value = metricFormatter(row.metricValues?.[0]?.value);
|
|
843
|
+
sections.push(`- ${label}: ${value} ${metricName}`);
|
|
844
|
+
}
|
|
845
|
+
sections.push("");
|
|
846
|
+
}
|
|
847
|
+
function renderTopPages(sections, report) {
|
|
848
|
+
const rows = report.rows ?? [];
|
|
849
|
+
if (rows.length === 0) return;
|
|
850
|
+
sections.push(
|
|
851
|
+
`#### Top pages by views (last 30 days)`,
|
|
852
|
+
"",
|
|
853
|
+
"_The most-visited pages \u2014 strong candidates for content-performance dashboards._",
|
|
854
|
+
""
|
|
855
|
+
);
|
|
856
|
+
sections.push("| Path | Page views | Active users | Avg session duration |");
|
|
857
|
+
sections.push("|------|------------|--------------|----------------------|");
|
|
858
|
+
for (const row of rows.slice(0, TOP_N_PAGES)) {
|
|
859
|
+
const path2 = truncate(row.dimensionValues?.[0]?.value, PAGE_PATH_MAX) || "-";
|
|
860
|
+
sections.push(
|
|
861
|
+
`| ${escapePipe(path2)} | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
|
|
862
|
+
row.metricValues?.[1]?.value
|
|
863
|
+
)} | ${formatDuration(row.metricValues?.[2]?.value)} |`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
sections.push("");
|
|
867
|
+
}
|
|
868
|
+
function renderTopEvents(sections, report) {
|
|
869
|
+
const rows = report.rows ?? [];
|
|
870
|
+
if (rows.length === 0) return;
|
|
871
|
+
sections.push(
|
|
872
|
+
`#### Top events (last 30 days)`,
|
|
873
|
+
"",
|
|
874
|
+
"_Standard auto-collected events (`page_view`, `session_start`, \u2026) plus any custom events the team has set up. The custom ones are usually the most interesting for funnel / conversion dashboards._",
|
|
875
|
+
""
|
|
876
|
+
);
|
|
877
|
+
sections.push("| Event | Event count | Unique users |");
|
|
878
|
+
sections.push("|-------|-------------|--------------|");
|
|
879
|
+
for (const row of rows.slice(0, TOP_N_EVENTS)) {
|
|
880
|
+
const name = truncate(row.dimensionValues?.[0]?.value, EVENT_NAME_MAX) || "-";
|
|
881
|
+
sections.push(
|
|
882
|
+
`| \`${escapePipe(name)}\` | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
|
|
883
|
+
row.metricValues?.[1]?.value
|
|
884
|
+
)} |`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
sections.push("");
|
|
888
|
+
}
|
|
889
|
+
function renderMetadata(sections, metadata) {
|
|
890
|
+
const dims = metadata.dimensions ?? [];
|
|
891
|
+
const mets = metadata.metrics ?? [];
|
|
892
|
+
if (dims.length === 0 && mets.length === 0) return;
|
|
893
|
+
const customDims = dims.filter((d) => d.customDefinition);
|
|
894
|
+
const customMets = mets.filter((m) => m.customDefinition);
|
|
895
|
+
const standardDims = dims.filter((d) => !d.customDefinition);
|
|
896
|
+
const standardMets = mets.filter((m) => !m.customDefinition);
|
|
897
|
+
sections.push("#### Available query surface", "");
|
|
898
|
+
sections.push(
|
|
899
|
+
"_Use these in custom `runReport` calls. Custom definitions reveal what the team specifically tracks beyond GA4's defaults._",
|
|
900
|
+
""
|
|
901
|
+
);
|
|
902
|
+
if (customDims.length > 0 || customMets.length > 0) {
|
|
903
|
+
sections.push(`##### Custom definitions`, "");
|
|
904
|
+
for (const d of customDims.slice(0, CUSTOM_METADATA_LIMIT)) {
|
|
905
|
+
const desc = d.description ? ` \u2014 ${truncate(d.description, DESCRIPTION_MAX)}` : "";
|
|
906
|
+
sections.push(`- dimension \`${d.apiName ?? d.uiName}\`${desc}`);
|
|
907
|
+
}
|
|
908
|
+
if (customDims.length > CUSTOM_METADATA_LIMIT) {
|
|
909
|
+
sections.push(
|
|
910
|
+
`- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
for (const m of customMets.slice(0, CUSTOM_METADATA_LIMIT)) {
|
|
914
|
+
const desc = m.description ? ` \u2014 ${truncate(m.description, DESCRIPTION_MAX)}` : "";
|
|
915
|
+
sections.push(`- metric \`${m.apiName ?? m.uiName}\`${desc}`);
|
|
916
|
+
}
|
|
917
|
+
if (customMets.length > CUSTOM_METADATA_LIMIT) {
|
|
918
|
+
sections.push(
|
|
919
|
+
`- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
sections.push("");
|
|
923
|
+
}
|
|
924
|
+
if (standardDims.length > 0) {
|
|
925
|
+
sections.push(`##### Standard dimensions (${standardDims.length})`);
|
|
926
|
+
const names = standardDims.slice(0, STANDARD_METADATA_LIMIT).map((d) => `\`${d.apiName ?? d.uiName}\``).join(", ");
|
|
927
|
+
const overflow = standardDims.length > STANDARD_METADATA_LIMIT ? ` _(+${standardDims.length - STANDARD_METADATA_LIMIT} more)_` : "";
|
|
928
|
+
sections.push(names + overflow, "");
|
|
929
|
+
}
|
|
930
|
+
if (standardMets.length > 0) {
|
|
931
|
+
sections.push(`##### Standard metrics (${standardMets.length})`);
|
|
932
|
+
const names = standardMets.slice(0, STANDARD_METADATA_LIMIT).map((m) => `\`${m.apiName ?? m.uiName}\``).join(", ");
|
|
933
|
+
const overflow = standardMets.length > STANDARD_METADATA_LIMIT ? ` _(+${standardMets.length - STANDARD_METADATA_LIMIT} more)_` : "";
|
|
934
|
+
sections.push(names + overflow, "");
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function renderProbeFailure(sections, propertyId, probe, serviceAccountEmail) {
|
|
938
|
+
sections.push(`#### Connection probe failed`, "");
|
|
939
|
+
if (probe.status === 403) {
|
|
940
|
+
sections.push(
|
|
941
|
+
`_The service account could not access GA4 property \`${propertyId}\`._`,
|
|
942
|
+
"",
|
|
943
|
+
`**Fix**: In GA4 (Admin > Property access management), grant **Viewer** role to the service account below, then re-run setup.`,
|
|
944
|
+
""
|
|
945
|
+
);
|
|
946
|
+
if (serviceAccountEmail) {
|
|
947
|
+
sections.push(`\`${serviceAccountEmail}\``, "");
|
|
948
|
+
}
|
|
949
|
+
} else if (probe.status === 404) {
|
|
950
|
+
sections.push(
|
|
951
|
+
`_GA4 property \`${propertyId}\` was not found. Double-check the Property ID in **Admin > Property Settings**._`,
|
|
952
|
+
""
|
|
953
|
+
);
|
|
954
|
+
} else if (probe.status === "network") {
|
|
955
|
+
sections.push(
|
|
956
|
+
`_Could not reach the GA4 Data API: ${probe.body}_`,
|
|
957
|
+
""
|
|
958
|
+
);
|
|
959
|
+
} else {
|
|
960
|
+
sections.push(
|
|
961
|
+
`_GA4 Data API returned ${probe.status}: ${probe.body.slice(0, 400)}_`,
|
|
962
|
+
""
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
async function exploreProperty(sections, params, propertyId, serviceAccountEmail) {
|
|
967
|
+
const probe = await probePropertyAccess(params, propertyId);
|
|
968
|
+
if (!probe.ok) {
|
|
969
|
+
renderProbeFailure(sections, propertyId, probe, serviceAccountEmail);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const batch1Requests = [
|
|
973
|
+
buildHeadlineRequest(),
|
|
974
|
+
buildTopDimensionRequest("sessionSourceMedium", "sessions", TOP_N_SOURCES),
|
|
975
|
+
buildTopDimensionRequest("country", "activeUsers", TOP_N_COUNTRIES),
|
|
976
|
+
buildTopDimensionRequest("deviceCategory", "sessions", 5),
|
|
977
|
+
buildTopDimensionRequest("browser", "activeUsers", TOP_N_BROWSERS)
|
|
978
|
+
];
|
|
979
|
+
const batch2Requests = [buildTopPagesRequest(), buildTopEventsRequest()];
|
|
980
|
+
const [batch1, batch2, metadata] = await Promise.all([
|
|
981
|
+
batchRunReports(params, propertyId, batch1Requests),
|
|
982
|
+
batchRunReports(params, propertyId, batch2Requests),
|
|
983
|
+
getMetadata(params, propertyId)
|
|
984
|
+
]);
|
|
985
|
+
const [headline, sourceMedium, country, device, browser] = batch1;
|
|
986
|
+
const [topPages, topEvents] = batch2;
|
|
987
|
+
if (headline) renderHeadline(sections, headline);
|
|
988
|
+
const hasTrafficSignal = (sourceMedium?.rows?.length ?? 0) + (country?.rows?.length ?? 0) + (device?.rows?.length ?? 0) + (browser?.rows?.length ?? 0) > 0;
|
|
989
|
+
if (hasTrafficSignal) {
|
|
990
|
+
sections.push(
|
|
991
|
+
"#### Traffic mix (last 30 days)",
|
|
992
|
+
"",
|
|
993
|
+
"_Where users are coming from \u2014 useful for dashboards that segment by acquisition channel, geography, or device._",
|
|
994
|
+
""
|
|
995
|
+
);
|
|
996
|
+
if (sourceMedium)
|
|
997
|
+
renderBreakdown(
|
|
998
|
+
sections,
|
|
999
|
+
"Top source / medium",
|
|
1000
|
+
"_Acquisition channels driving sessions._",
|
|
1001
|
+
sourceMedium,
|
|
1002
|
+
TOP_N_SOURCES
|
|
1003
|
+
);
|
|
1004
|
+
if (country)
|
|
1005
|
+
renderBreakdown(
|
|
1006
|
+
sections,
|
|
1007
|
+
"Top countries by active users",
|
|
1008
|
+
"_Geography \u2014 informs which date/time filters and locale segments to expose._",
|
|
1009
|
+
country,
|
|
1010
|
+
TOP_N_COUNTRIES
|
|
1011
|
+
);
|
|
1012
|
+
if (device)
|
|
1013
|
+
renderBreakdown(
|
|
1014
|
+
sections,
|
|
1015
|
+
"Device categories",
|
|
1016
|
+
"_Desktop / mobile / tablet split \u2014 informs responsive cuts._",
|
|
1017
|
+
device,
|
|
1018
|
+
5
|
|
1019
|
+
);
|
|
1020
|
+
if (browser)
|
|
1021
|
+
renderBreakdown(
|
|
1022
|
+
sections,
|
|
1023
|
+
"Top browsers",
|
|
1024
|
+
"_Browser distribution \u2014 informs compatibility dashboards or QA filters._",
|
|
1025
|
+
browser,
|
|
1026
|
+
TOP_N_BROWSERS
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
if (topPages) renderTopPages(sections, topPages);
|
|
1030
|
+
if (topEvents) renderTopEvents(sections, topEvents);
|
|
1031
|
+
if (metadata) renderMetadata(sections, metadata);
|
|
578
1032
|
}
|
|
579
1033
|
var googleAnalyticsSetupFlow = {
|
|
580
1034
|
initialState: () => ({}),
|
|
@@ -587,15 +1041,19 @@ var googleAnalyticsSetupFlow = {
|
|
|
587
1041
|
en: "Select a Google Analytics account"
|
|
588
1042
|
},
|
|
589
1043
|
async fetchOptions(_state, rt) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1044
|
+
try {
|
|
1045
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
1046
|
+
return summaries.map((s) => {
|
|
1047
|
+
const accountResource = s.account ?? s.name ?? "";
|
|
1048
|
+
if (!accountResource) return null;
|
|
1049
|
+
return {
|
|
1050
|
+
value: accountResource,
|
|
1051
|
+
label: s.displayName ?? accountResource
|
|
1052
|
+
};
|
|
1053
|
+
}).filter((v) => v != null);
|
|
1054
|
+
} catch {
|
|
1055
|
+
return [];
|
|
1056
|
+
}
|
|
599
1057
|
},
|
|
600
1058
|
applyAnswer: (state, answer) => ({ ...state, account: answer[0] })
|
|
601
1059
|
},
|
|
@@ -608,81 +1066,153 @@ var googleAnalyticsSetupFlow = {
|
|
|
608
1066
|
},
|
|
609
1067
|
async fetchOptions(state, rt) {
|
|
610
1068
|
if (!state.account) return [];
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1069
|
+
try {
|
|
1070
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
1071
|
+
const account = summaries.find(
|
|
1072
|
+
(s) => (s.account ?? s.name) === state.account
|
|
1073
|
+
);
|
|
1074
|
+
const props = (account?.propertySummaries ?? []).map((p) => {
|
|
1075
|
+
const id = propertyIdFromResource(p.property);
|
|
1076
|
+
if (!id) return null;
|
|
1077
|
+
return {
|
|
1078
|
+
value: id,
|
|
1079
|
+
label: p.displayName ? `${p.displayName} (${id})` : id
|
|
1080
|
+
};
|
|
1081
|
+
}).filter((v) => v != null);
|
|
1082
|
+
if (props.length === 0) return [];
|
|
1083
|
+
return [
|
|
1084
|
+
{
|
|
1085
|
+
value: ALL_PROPERTIES,
|
|
1086
|
+
label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30D1\u30C6\u30A3" : "All properties"
|
|
1087
|
+
},
|
|
1088
|
+
...props
|
|
1089
|
+
];
|
|
1090
|
+
} catch {
|
|
1091
|
+
return [];
|
|
1092
|
+
}
|
|
631
1093
|
},
|
|
632
1094
|
applyAnswer: (state, answer) => ({ ...state, properties: answer })
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
slug: "manualPropertyId",
|
|
1098
|
+
type: "text",
|
|
1099
|
+
question: {
|
|
1100
|
+
ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002\u5165\u529B\u5F8C\u3001\u30C7\u30FC\u30BF\u53D6\u5F97\u3092\u8A66\u3057\u3066\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u63D0\u6848\u7528\u306E\u30B5\u30DE\u30EA\u3092\u751F\u6210\u3057\u307E\u3059\u3002",
|
|
1101
|
+
en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings. After entry we'll probe the property via the Data API and assemble a dashboard-ready summary."
|
|
1102
|
+
},
|
|
1103
|
+
async fetchOptions(state) {
|
|
1104
|
+
if (state.properties?.length) return [];
|
|
1105
|
+
return [{ value: "_show", label: "" }];
|
|
1106
|
+
},
|
|
1107
|
+
applyAnswer: (state, answer) => ({
|
|
1108
|
+
...state,
|
|
1109
|
+
manualPropertyId: answer[0]
|
|
1110
|
+
})
|
|
633
1111
|
}
|
|
634
1112
|
],
|
|
635
1113
|
async finalize(state, rt) {
|
|
636
|
-
|
|
637
|
-
|
|
1114
|
+
const sections = ["## Google Analytics", ""];
|
|
1115
|
+
let serviceAccountEmail = null;
|
|
1116
|
+
try {
|
|
1117
|
+
const keyJsonBase64 = rt.params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
1118
|
+
if (keyJsonBase64) {
|
|
1119
|
+
serviceAccountEmail = decodeServiceAccount(keyJsonBase64).client_email ?? null;
|
|
1120
|
+
}
|
|
1121
|
+
} catch {
|
|
1122
|
+
}
|
|
1123
|
+
let targetPropertyIds = [];
|
|
1124
|
+
let accountLabel = null;
|
|
1125
|
+
let accountSummary;
|
|
1126
|
+
if (state.account && state.properties) {
|
|
1127
|
+
try {
|
|
1128
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
1129
|
+
accountSummary = summaries.find(
|
|
1130
|
+
(s) => (s.account ?? s.name) === state.account
|
|
1131
|
+
);
|
|
1132
|
+
accountLabel = accountSummary?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
1133
|
+
} catch {
|
|
1134
|
+
accountLabel = state.account.replace(/^accounts\//, "");
|
|
1135
|
+
}
|
|
1136
|
+
targetPropertyIds = await resolveSetupSelection({
|
|
1137
|
+
selected: state.properties,
|
|
1138
|
+
allSentinel: ALL_PROPERTIES,
|
|
1139
|
+
fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
1140
|
+
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
1141
|
+
});
|
|
1142
|
+
} else if (state.manualPropertyId) {
|
|
1143
|
+
targetPropertyIds = [state.manualPropertyId];
|
|
638
1144
|
}
|
|
639
|
-
const summaries = await listAccountSummaries(rt.params);
|
|
640
|
-
const account = summaries.find(
|
|
641
|
-
(s) => (s.account ?? s.name) === state.account
|
|
642
|
-
);
|
|
643
|
-
const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
644
|
-
const targetPropertyIds = await resolveSetupSelection({
|
|
645
|
-
selected: state.properties,
|
|
646
|
-
allSentinel: ALL_PROPERTIES,
|
|
647
|
-
fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
648
|
-
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
649
|
-
});
|
|
650
|
-
const sections = [
|
|
651
|
-
"## Google Analytics",
|
|
652
|
-
"",
|
|
653
|
-
`### Account: ${accountLabel}`,
|
|
654
|
-
""
|
|
655
|
-
];
|
|
656
1145
|
if (targetPropertyIds.length === 0) {
|
|
657
|
-
sections.push(
|
|
1146
|
+
sections.push(
|
|
1147
|
+
"_Could not identify a target GA4 property. Either enable the Google Analytics Admin API on the service account's GCP project, or re-run setup and provide the Property ID manually._",
|
|
1148
|
+
""
|
|
1149
|
+
);
|
|
1150
|
+
if (serviceAccountEmail) {
|
|
1151
|
+
sections.push(
|
|
1152
|
+
`_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
|
|
1153
|
+
""
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
658
1156
|
return sections.join("\n");
|
|
659
1157
|
}
|
|
660
|
-
sections.push(
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1158
|
+
sections.push(
|
|
1159
|
+
"_The downstream coding agent should use this summary as the canonical baseline of the property's traffic shape for dashboard / app proposals. Headline metrics, traffic mix, top pages, top events, and custom dimensions/metrics together cover the most common dashboarding patterns; reach for the standard query surface for anything custom._",
|
|
1160
|
+
""
|
|
1161
|
+
);
|
|
1162
|
+
if (accountLabel) sections.push(`### Account: ${accountLabel}`, "");
|
|
1163
|
+
sections.push("### Properties", "");
|
|
1164
|
+
sections.push(
|
|
1165
|
+
"| Property ID | Display Name | Time Zone | Currency | Industry |"
|
|
1166
|
+
);
|
|
1167
|
+
sections.push(
|
|
1168
|
+
"|-------------|--------------|-----------|----------|----------|"
|
|
1169
|
+
);
|
|
1170
|
+
const properties = await Promise.all(
|
|
1171
|
+
targetPropertyIds.map((id) => getProperty(rt.params, id))
|
|
1172
|
+
);
|
|
1173
|
+
for (let i = 0; i < targetPropertyIds.length; i++) {
|
|
1174
|
+
const pid = targetPropertyIds[i];
|
|
1175
|
+
const prop = properties[i];
|
|
1176
|
+
const displayName = escapePipe(prop?.displayName ?? "-");
|
|
665
1177
|
const timeZone = prop?.timeZone ?? "-";
|
|
666
1178
|
const currency = prop?.currencyCode ?? "-";
|
|
667
|
-
const industry = (prop?.industryCategory ?? "-")
|
|
1179
|
+
const industry = escapePipe(prop?.industryCategory ?? "-");
|
|
668
1180
|
sections.push(
|
|
669
|
-
`| ${
|
|
1181
|
+
`| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
|
|
670
1182
|
);
|
|
671
1183
|
}
|
|
672
1184
|
sections.push("");
|
|
1185
|
+
const propertiesToExplore = targetPropertyIds.slice(
|
|
1186
|
+
0,
|
|
1187
|
+
MAX_PROPERTIES_TO_EXPLORE
|
|
1188
|
+
);
|
|
1189
|
+
for (let i = 0; i < propertiesToExplore.length; i++) {
|
|
1190
|
+
const pid = propertiesToExplore[i];
|
|
1191
|
+
const prop = properties[i];
|
|
1192
|
+
const heading = prop?.displayName && prop.displayName !== "-" ? `### Data exploration: ${prop.displayName} (${pid})` : `### Data exploration: property ${pid}`;
|
|
1193
|
+
sections.push(heading, "");
|
|
1194
|
+
await exploreProperty(sections, rt.params, pid, serviceAccountEmail);
|
|
1195
|
+
}
|
|
1196
|
+
if (targetPropertyIds.length > propertiesToExplore.length) {
|
|
1197
|
+
const skipped = targetPropertyIds.length - propertiesToExplore.length;
|
|
1198
|
+
sections.push(
|
|
1199
|
+
`_Deep exploration limited to the first ${propertiesToExplore.length} of ${targetPropertyIds.length} properties to keep this summary bounded. The remaining ${skipped} can be explored on demand via \`connector_google-analytics-service-account_request\`._`,
|
|
1200
|
+
""
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
673
1203
|
return sections.join("\n");
|
|
674
1204
|
}
|
|
675
1205
|
};
|
|
676
1206
|
|
|
677
1207
|
// ../connectors/src/connectors/google-analytics/tools/request.ts
|
|
678
1208
|
import { z } from "zod";
|
|
679
|
-
var BASE_URL2 = "https://analyticsdata.googleapis.com
|
|
1209
|
+
var BASE_URL2 = "https://analyticsdata.googleapis.com";
|
|
680
1210
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
681
1211
|
var inputSchema = z.object({
|
|
682
1212
|
toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
|
|
683
1213
|
connectionId: z.string().describe("ID of the Google Analytics connection to use"),
|
|
684
1214
|
method: z.enum(["GET", "POST"]).describe("HTTP method"),
|
|
685
|
-
path: z.string().describe("
|
|
1215
|
+
path: z.string().describe("Full request path including the API version prefix, appended to https://analyticsdata.googleapis.com. Use the same form as the GA4 Data API reference, e.g., '/v1beta/properties/123456789:runReport'."),
|
|
686
1216
|
body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
|
|
687
1217
|
});
|
|
688
1218
|
var outputSchema = z.discriminatedUnion("success", [
|
|
@@ -699,8 +1229,7 @@ var outputSchema = z.discriminatedUnion("success", [
|
|
|
699
1229
|
var requestTool = new ConnectorTool({
|
|
700
1230
|
name: "request",
|
|
701
1231
|
description: `Send authenticated requests to the Google Analytics Data API.
|
|
702
|
-
Authentication is handled automatically using a service account
|
|
703
|
-
{propertyId} in the path is automatically replaced with the connection's property-id.`,
|
|
1232
|
+
Authentication is handled automatically using a service account.`,
|
|
704
1233
|
inputSchema,
|
|
705
1234
|
outputSchema,
|
|
706
1235
|
async execute({ connectionId, method, path: path2, body }, connections) {
|
|
@@ -712,7 +1241,6 @@ Authentication is handled automatically using a service account.
|
|
|
712
1241
|
try {
|
|
713
1242
|
const { GoogleAuth } = await import("google-auth-library");
|
|
714
1243
|
const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
|
|
715
|
-
const propertyId = parameters.propertyId.getValue(connection2);
|
|
716
1244
|
const credentials = JSON.parse(
|
|
717
1245
|
Buffer.from(keyJsonBase64, "base64").toString("utf-8")
|
|
718
1246
|
);
|
|
@@ -724,8 +1252,7 @@ Authentication is handled automatically using a service account.
|
|
|
724
1252
|
if (!token) {
|
|
725
1253
|
return { success: false, error: "Failed to obtain access token" };
|
|
726
1254
|
}
|
|
727
|
-
const
|
|
728
|
-
const url = `${BASE_URL2}${resolvedPath}`;
|
|
1255
|
+
const url = `${BASE_URL2}${path2}`;
|
|
729
1256
|
const controller = new AbortController();
|
|
730
1257
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
731
1258
|
try {
|
|
@@ -772,7 +1299,7 @@ var googleAnalyticsConnector = new ConnectorPlugin({
|
|
|
772
1299
|
systemPrompt: {
|
|
773
1300
|
en: `### Tools
|
|
774
1301
|
|
|
775
|
-
- \`
|
|
1302
|
+
- \`connector_google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. See the GA4 Data API Reference below for available endpoints and request bodies.
|
|
776
1303
|
|
|
777
1304
|
### Business Logic
|
|
778
1305
|
|
|
@@ -781,9 +1308,11 @@ The business logic type for this connector is "typescript". Use the connector SD
|
|
|
781
1308
|
SDK methods (client created via \`connection(connectionId)\`):
|
|
782
1309
|
- \`client.runReport(request)\` \u2014 run a GA4 report
|
|
783
1310
|
- \`client.runRealtimeReport(request)\` \u2014 run a realtime report
|
|
784
|
-
- \`client.getMetadata()\` \u2014 fetch available dimensions/metrics
|
|
1311
|
+
- \`client.getMetadata(request)\` \u2014 fetch available dimensions/metrics
|
|
785
1312
|
- \`client.request(path, init?)\` \u2014 low-level authenticated fetch
|
|
786
1313
|
|
|
1314
|
+
**IMPORTANT**: You MUST always include \`propertyId\` in every SDK method call. The property ID is obtained during the setup flow. Without it, the request will fail.
|
|
1315
|
+
|
|
787
1316
|
\`\`\`ts
|
|
788
1317
|
import type { Context } from "hono";
|
|
789
1318
|
import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
@@ -791,12 +1320,14 @@ import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
|
791
1320
|
const ga = connection("<connectionId>");
|
|
792
1321
|
|
|
793
1322
|
export default async function handler(c: Context) {
|
|
794
|
-
const { startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
1323
|
+
const { propertyId, startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
1324
|
+
propertyId: string;
|
|
795
1325
|
startDate?: string;
|
|
796
1326
|
endDate?: string;
|
|
797
1327
|
}>();
|
|
798
1328
|
|
|
799
1329
|
const { rows } = await ga.runReport({
|
|
1330
|
+
propertyId,
|
|
800
1331
|
dateRanges: [{ startDate, endDate }],
|
|
801
1332
|
dimensions: [{ name: "date" }],
|
|
802
1333
|
metrics: [{ name: "activeUsers" }, { name: "sessions" }],
|
|
@@ -817,8 +1348,8 @@ export default async function handler(c: Context) {
|
|
|
817
1348
|
|
|
818
1349
|
Common operations:
|
|
819
1350
|
|
|
820
|
-
- **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
|
|
821
|
-
- **Run report** (query analytics data): \`POST properties/{propertyId}:runReport\` with a body like:
|
|
1351
|
+
- **Get metadata** (list available dimensions/metrics for a property): \`GET /v1beta/properties/{propertyId}/metadata\`
|
|
1352
|
+
- **Run report** (query analytics data): \`POST /v1beta/properties/{propertyId}:runReport\` with a body like:
|
|
822
1353
|
\`\`\`json
|
|
823
1354
|
{
|
|
824
1355
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -827,7 +1358,7 @@ Common operations:
|
|
|
827
1358
|
"limit": 100
|
|
828
1359
|
}
|
|
829
1360
|
\`\`\`
|
|
830
|
-
- **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
|
|
1361
|
+
- **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
|
|
831
1362
|
|
|
832
1363
|
#### Common dimensions
|
|
833
1364
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -840,7 +1371,7 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
840
1371
|
- Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
|
|
841
1372
|
ja: `### \u30C4\u30FC\u30EB
|
|
842
1373
|
|
|
843
|
-
- \`
|
|
1374
|
+
- \`connector_google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
844
1375
|
|
|
845
1376
|
### Business Logic
|
|
846
1377
|
|
|
@@ -849,9 +1380,11 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
849
1380
|
SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
|
|
850
1381
|
- \`client.runReport(request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
851
1382
|
- \`client.runRealtimeReport(request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
852
|
-
- \`client.getMetadata()\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
|
|
1383
|
+
- \`client.getMetadata(request)\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
|
|
853
1384
|
- \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
|
|
854
1385
|
|
|
1386
|
+
**\u91CD\u8981**: \u3059\u3079\u3066\u306ESDK\u30E1\u30BD\u30C3\u30C9\u547C\u3073\u51FA\u3057\u306B\u306F\u5FC5\u305A \`propertyId\` \u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u30D7\u30ED\u30D1\u30C6\u30A3ID\u306F\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u30D5\u30ED\u30FC\u3067\u53D6\u5F97\u3055\u308C\u307E\u3059\u3002\u6307\u5B9A\u3057\u306A\u3044\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u5931\u6557\u3057\u307E\u3059\u3002
|
|
1387
|
+
|
|
855
1388
|
\`\`\`ts
|
|
856
1389
|
import type { Context } from "hono";
|
|
857
1390
|
import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
@@ -859,12 +1392,14 @@ import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
|
859
1392
|
const ga = connection("<connectionId>");
|
|
860
1393
|
|
|
861
1394
|
export default async function handler(c: Context) {
|
|
862
|
-
const { startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
1395
|
+
const { propertyId, startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
|
|
1396
|
+
propertyId: string;
|
|
863
1397
|
startDate?: string;
|
|
864
1398
|
endDate?: string;
|
|
865
1399
|
}>();
|
|
866
1400
|
|
|
867
1401
|
const { rows } = await ga.runReport({
|
|
1402
|
+
propertyId,
|
|
868
1403
|
dateRanges: [{ startDate, endDate }],
|
|
869
1404
|
dimensions: [{ name: "date" }],
|
|
870
1405
|
metrics: [{ name: "activeUsers" }, { name: "sessions" }],
|
|
@@ -885,8 +1420,8 @@ export default async function handler(c: Context) {
|
|
|
885
1420
|
|
|
886
1421
|
\u4E3B\u306A\u64CD\u4F5C:
|
|
887
1422
|
|
|
888
|
-
- **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET properties/{propertyId}/metadata\`
|
|
889
|
-
- **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
|
|
1423
|
+
- **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET /v1beta/properties/{propertyId}/metadata\`
|
|
1424
|
+
- **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST /v1beta/properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
|
|
890
1425
|
\`\`\`json
|
|
891
1426
|
{
|
|
892
1427
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -895,7 +1430,7 @@ export default async function handler(c: Context) {
|
|
|
895
1430
|
"limit": 100
|
|
896
1431
|
}
|
|
897
1432
|
\`\`\`
|
|
898
|
-
- **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST properties/{propertyId}:runRealtimeReport\`
|
|
1433
|
+
- **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
|
|
899
1434
|
|
|
900
1435
|
#### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
|
|
901
1436
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -917,13 +1452,27 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
917
1452
|
error: "google-analytics: missing service account key"
|
|
918
1453
|
};
|
|
919
1454
|
}
|
|
1455
|
+
const propertyId = params[parameters.propertyId.slug];
|
|
1456
|
+
if (!propertyId) {
|
|
1457
|
+
try {
|
|
1458
|
+
const sa = decodeServiceAccount(keyJsonBase64);
|
|
1459
|
+
await getAccessToken(sa);
|
|
1460
|
+
return { success: true };
|
|
1461
|
+
} catch (err) {
|
|
1462
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1463
|
+
return { success: false, error: msg };
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
920
1466
|
try {
|
|
921
|
-
const res = await
|
|
1467
|
+
const res = await dataFetch(
|
|
1468
|
+
params,
|
|
1469
|
+
`/properties/${propertyId}/metadata`
|
|
1470
|
+
);
|
|
922
1471
|
if (!res.ok) {
|
|
923
1472
|
const body = await res.text().catch(() => res.statusText);
|
|
924
1473
|
return {
|
|
925
1474
|
success: false,
|
|
926
|
-
error: `
|
|
1475
|
+
error: `Google Analytics API failed: HTTP ${res.status} ${body}`
|
|
927
1476
|
};
|
|
928
1477
|
}
|
|
929
1478
|
return { success: true };
|