@squadbase/vite-server 0.1.17-dev.3b633bb → 0.1.17-dev.71a85cd
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 +3310 -742
- package/dist/connectors/airtable-oauth.js +50 -8
- package/dist/connectors/airtable.js +46 -8
- package/dist/connectors/amplitude.js +10 -8
- package/dist/connectors/anthropic.js +4 -2
- package/dist/connectors/asana.js +39 -10
- package/dist/connectors/attio.js +32 -13
- package/dist/connectors/aws-billing.js +10 -8
- package/dist/connectors/azure-sql.js +33 -7
- package/dist/connectors/backlog-api-key.js +42 -15
- package/dist/connectors/clickup.js +52 -10
- package/dist/connectors/cosmosdb.js +14 -12
- package/dist/connectors/customerio.js +10 -8
- package/dist/connectors/dbt.js +688 -25
- package/dist/connectors/freshdesk.js +84 -8
- package/dist/connectors/freshsales.js +10 -8
- package/dist/connectors/freshservice.js +10 -8
- package/dist/connectors/gamma.js +17 -15
- package/dist/connectors/gemini.js +4 -2
- package/dist/connectors/github.js +14 -12
- package/dist/connectors/gmail-oauth.js +10 -10
- package/dist/connectors/gmail.js +6 -4
- package/dist/connectors/google-ads.js +10 -8
- package/dist/connectors/google-analytics-oauth.js +154 -25
- package/dist/connectors/google-analytics.js +536 -109
- package/dist/connectors/google-audit-log.js +6 -4
- package/dist/connectors/google-calendar-oauth.js +63 -15
- package/dist/connectors/google-calendar.js +63 -11
- package/dist/connectors/google-docs.js +10 -10
- package/dist/connectors/google-drive.js +32 -10
- package/dist/connectors/google-search-console-oauth.js +128 -17
- package/dist/connectors/google-sheets.js +8 -6
- package/dist/connectors/google-slides.js +10 -10
- package/dist/connectors/grafana.js +47 -10
- package/dist/connectors/hubspot-oauth.js +41 -9
- package/dist/connectors/hubspot.js +27 -9
- package/dist/connectors/influxdb.js +10 -8
- package/dist/connectors/intercom-oauth.js +72 -12
- package/dist/connectors/intercom.js +14 -12
- package/dist/connectors/jdbc.js +8 -6
- package/dist/connectors/jira-api-key.js +70 -11
- package/dist/connectors/kintone-api-token.js +68 -18
- package/dist/connectors/kintone.js +56 -11
- package/dist/connectors/linear.js +56 -12
- package/dist/connectors/linkedin-ads.js +43 -14
- package/dist/connectors/mailchimp-oauth.js +8 -6
- package/dist/connectors/mailchimp.js +8 -6
- package/dist/connectors/meta-ads-oauth.js +35 -14
- package/dist/connectors/meta-ads.js +37 -14
- package/dist/connectors/mixpanel.js +10 -8
- package/dist/connectors/monday.js +11 -9
- package/dist/connectors/mongodb.js +10 -8
- package/dist/connectors/notion-oauth.js +60 -11
- package/dist/connectors/notion.js +62 -11
- package/dist/connectors/openai.js +4 -2
- package/dist/connectors/oracle.js +25 -7
- package/dist/connectors/outlook-oauth.js +21 -21
- package/dist/connectors/powerbi-oauth.js +13 -13
- package/dist/connectors/salesforce.js +44 -9
- package/dist/connectors/semrush.js +8 -6
- package/dist/connectors/sentry.js +38 -10
- package/dist/connectors/shopify-oauth.js +43 -10
- package/dist/connectors/shopify.js +10 -8
- package/dist/connectors/sqlserver.js +33 -7
- package/dist/connectors/stripe-api-key.js +68 -15
- package/dist/connectors/stripe-oauth.js +70 -19
- package/dist/connectors/supabase.js +24 -5
- package/dist/connectors/tableau.js +17 -15
- package/dist/connectors/tiktok-ads.js +39 -16
- package/dist/connectors/wix-store.js +10 -8
- package/dist/connectors/zendesk-oauth.js +55 -12
- package/dist/connectors/zendesk.js +14 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3339 -746
- package/dist/main.js +3328 -740
- package/dist/vite-plugin.js +3308 -740
- 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.
|
|
@@ -67,6 +69,16 @@ var parameters = {
|
|
|
67
69
|
type: "base64EncodedJson",
|
|
68
70
|
secret: true,
|
|
69
71
|
required: true
|
|
72
|
+
}),
|
|
73
|
+
propertyId: new ParameterDefinition({
|
|
74
|
+
slug: "property-id",
|
|
75
|
+
name: "Google Analytics Property ID",
|
|
76
|
+
description: "The Google Analytics 4 property ID (e.g., 123456789). Automatically set during the setup flow.",
|
|
77
|
+
envVarBaseKey: "GA_PROPERTY_ID",
|
|
78
|
+
type: "text",
|
|
79
|
+
secret: false,
|
|
80
|
+
required: false,
|
|
81
|
+
isDeprecated: true
|
|
70
82
|
})
|
|
71
83
|
};
|
|
72
84
|
|
|
@@ -98,6 +110,7 @@ function buildJwt(clientEmail, privateKey, nowSec) {
|
|
|
98
110
|
}
|
|
99
111
|
function createClient(params) {
|
|
100
112
|
const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
113
|
+
const defaultPropertyId = params["property-id"] ?? "";
|
|
101
114
|
if (!serviceAccountKeyJsonBase64) {
|
|
102
115
|
throw new Error(
|
|
103
116
|
`google-analytics: missing required parameters: ${parameters.serviceAccountKeyJsonBase64.slug}`
|
|
@@ -159,13 +172,18 @@ function createClient(params) {
|
|
|
159
172
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
160
173
|
return fetch(url, { ...init, headers });
|
|
161
174
|
},
|
|
162
|
-
async runReport(
|
|
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
|
+
}
|
|
163
181
|
const response = await this.request(
|
|
164
182
|
`properties/${propertyId}:runReport`,
|
|
165
183
|
{
|
|
166
184
|
method: "POST",
|
|
167
185
|
headers: { "Content-Type": "application/json" },
|
|
168
|
-
body: JSON.stringify(
|
|
186
|
+
body: JSON.stringify(body)
|
|
169
187
|
}
|
|
170
188
|
);
|
|
171
189
|
if (!response.ok) {
|
|
@@ -180,7 +198,11 @@ function createClient(params) {
|
|
|
180
198
|
rowCount: data.rowCount ?? 0
|
|
181
199
|
};
|
|
182
200
|
},
|
|
183
|
-
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
|
+
}
|
|
184
206
|
const response = await this.request(
|
|
185
207
|
`properties/${propertyId}/metadata`,
|
|
186
208
|
{ method: "GET" }
|
|
@@ -193,13 +215,18 @@ function createClient(params) {
|
|
|
193
215
|
}
|
|
194
216
|
return await response.json();
|
|
195
217
|
},
|
|
196
|
-
async runRealtimeReport(
|
|
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
|
+
}
|
|
197
224
|
const response = await this.request(
|
|
198
225
|
`properties/${propertyId}:runRealtimeReport`,
|
|
199
226
|
{
|
|
200
227
|
method: "POST",
|
|
201
228
|
headers: { "Content-Type": "application/json" },
|
|
202
|
-
body: JSON.stringify(
|
|
229
|
+
body: JSON.stringify(body)
|
|
203
230
|
}
|
|
204
231
|
);
|
|
205
232
|
if (!response.ok) {
|
|
@@ -323,7 +350,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
323
350
|
/**
|
|
324
351
|
* Create tools for connections that belong to this connector.
|
|
325
352
|
* Filters connections by connectorKey internally.
|
|
326
|
-
* Returns tools keyed as
|
|
353
|
+
* Returns tools keyed as `connector_${connectorKey}_${toolName}`.
|
|
327
354
|
*/
|
|
328
355
|
createTools(connections, config, opts) {
|
|
329
356
|
const myConnections = connections.filter(
|
|
@@ -333,7 +360,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
333
360
|
for (const t of Object.values(this.tools)) {
|
|
334
361
|
const tool = t.createTool(myConnections, config);
|
|
335
362
|
const originalToModelOutput = tool.toModelOutput;
|
|
336
|
-
result[
|
|
363
|
+
result[`connector_${this.connectorKey}_${t.name}`] = {
|
|
337
364
|
...tool,
|
|
338
365
|
toModelOutput: async (options) => {
|
|
339
366
|
if (!originalToModelOutput) {
|
|
@@ -463,10 +490,10 @@ var AUTH_TYPES = {
|
|
|
463
490
|
// ../connectors/src/connectors/google-analytics/setup.ts
|
|
464
491
|
var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
465
492
|
dataOverviewInstructions: {
|
|
466
|
-
en: `1. Call
|
|
467
|
-
2. Call
|
|
468
|
-
ja: `1.
|
|
469
|
-
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`
|
|
470
497
|
}
|
|
471
498
|
});
|
|
472
499
|
|
|
@@ -574,7 +601,17 @@ async function dataFetch(params, path2, init) {
|
|
|
574
601
|
// ../connectors/src/connectors/google-analytics/setup-flow.ts
|
|
575
602
|
var ALL_PROPERTIES = "__ALL_PROPERTIES__";
|
|
576
603
|
var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
|
|
577
|
-
var
|
|
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;
|
|
578
615
|
async function listAccountSummaries(params) {
|
|
579
616
|
const summaries = [];
|
|
580
617
|
let pageToken;
|
|
@@ -597,9 +634,13 @@ function propertyIdFromResource(resource) {
|
|
|
597
634
|
return (resource ?? "").replace(/^properties\//, "");
|
|
598
635
|
}
|
|
599
636
|
async function getProperty(params, propertyId) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
+
}
|
|
603
644
|
}
|
|
604
645
|
async function getMetadata(params, propertyId) {
|
|
605
646
|
try {
|
|
@@ -607,41 +648,387 @@ async function getMetadata(params, propertyId) {
|
|
|
607
648
|
params,
|
|
608
649
|
`/properties/${propertyId}/metadata`
|
|
609
650
|
);
|
|
610
|
-
if (!res.ok) return
|
|
611
|
-
|
|
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) {
|
|
612
676
|
return {
|
|
613
|
-
|
|
614
|
-
|
|
677
|
+
ok: false,
|
|
678
|
+
status: "network",
|
|
679
|
+
body: err instanceof Error ? err.message : String(err)
|
|
615
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] ?? {});
|
|
616
698
|
} catch {
|
|
617
|
-
return
|
|
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
|
+
);
|
|
618
886
|
}
|
|
887
|
+
sections.push("");
|
|
619
888
|
}
|
|
620
|
-
function
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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}`);
|
|
625
907
|
}
|
|
626
|
-
if (
|
|
908
|
+
if (customDims.length > CUSTOM_METADATA_LIMIT) {
|
|
627
909
|
sections.push(
|
|
628
|
-
`- \u2026and ${
|
|
910
|
+
`- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
|
|
629
911
|
);
|
|
630
912
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
sections.push(`#### Metrics (${metrics.length})`, "");
|
|
635
|
-
for (const m of metrics.slice(0, METADATA_DISPLAY_LIMIT)) {
|
|
636
|
-
sections.push(`- ${m.apiName ?? m.uiName ?? "(unknown)"}`);
|
|
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}`);
|
|
637
916
|
}
|
|
638
|
-
if (
|
|
917
|
+
if (customMets.length > CUSTOM_METADATA_LIMIT) {
|
|
639
918
|
sections.push(
|
|
640
|
-
`- \u2026and ${
|
|
919
|
+
`- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
|
|
641
920
|
);
|
|
642
921
|
}
|
|
643
922
|
sections.push("");
|
|
644
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);
|
|
645
1032
|
}
|
|
646
1033
|
var googleAnalyticsSetupFlow = {
|
|
647
1034
|
initialState: () => ({}),
|
|
@@ -710,8 +1097,8 @@ var googleAnalyticsSetupFlow = {
|
|
|
710
1097
|
slug: "manualPropertyId",
|
|
711
1098
|
type: "text",
|
|
712
1099
|
question: {
|
|
713
|
-
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",
|
|
714
|
-
en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings."
|
|
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."
|
|
715
1102
|
},
|
|
716
1103
|
async fetchOptions(state) {
|
|
717
1104
|
if (state.properties?.length) return [];
|
|
@@ -725,85 +1112,107 @@ var googleAnalyticsSetupFlow = {
|
|
|
725
1112
|
],
|
|
726
1113
|
async finalize(state, rt) {
|
|
727
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;
|
|
728
1126
|
if (state.account && state.properties) {
|
|
729
|
-
let summaries = [];
|
|
730
1127
|
try {
|
|
731
|
-
summaries = await listAccountSummaries(rt.params);
|
|
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\//, "");
|
|
732
1133
|
} catch {
|
|
1134
|
+
accountLabel = state.account.replace(/^accounts\//, "");
|
|
733
1135
|
}
|
|
734
|
-
|
|
735
|
-
(s) => (s.account ?? s.name) === state.account
|
|
736
|
-
);
|
|
737
|
-
const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
738
|
-
const targetPropertyIds = await resolveSetupSelection({
|
|
1136
|
+
targetPropertyIds = await resolveSetupSelection({
|
|
739
1137
|
selected: state.properties,
|
|
740
1138
|
allSentinel: ALL_PROPERTIES,
|
|
741
|
-
fetchAll: async () => (
|
|
1139
|
+
fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
742
1140
|
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
743
1141
|
});
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
sections.push(
|
|
750
|
-
"| Property ID | Display Name | Time Zone | Currency | Industry |"
|
|
751
|
-
);
|
|
1142
|
+
} else if (state.manualPropertyId) {
|
|
1143
|
+
targetPropertyIds = [state.manualPropertyId];
|
|
1144
|
+
}
|
|
1145
|
+
if (targetPropertyIds.length === 0) {
|
|
752
1146
|
sections.push(
|
|
753
|
-
"
|
|
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
|
+
""
|
|
754
1149
|
);
|
|
755
|
-
|
|
756
|
-
const prop = await getProperty(rt.params, pid).catch(() => null);
|
|
757
|
-
const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
|
|
758
|
-
const timeZone = prop?.timeZone ?? "-";
|
|
759
|
-
const currency = prop?.currencyCode ?? "-";
|
|
760
|
-
const industry = (prop?.industryCategory ?? "-").replace(
|
|
761
|
-
/\|/g,
|
|
762
|
-
"\\|"
|
|
763
|
-
);
|
|
1150
|
+
if (serviceAccountEmail) {
|
|
764
1151
|
sections.push(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
sections.push("");
|
|
769
|
-
const firstPropertyId = targetPropertyIds[0];
|
|
770
|
-
if (firstPropertyId) {
|
|
771
|
-
const { dimensions, metrics } = await getMetadata(
|
|
772
|
-
rt.params,
|
|
773
|
-
firstPropertyId
|
|
1152
|
+
`_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
|
|
1153
|
+
""
|
|
774
1154
|
);
|
|
775
|
-
appendMetadataSection(sections, dimensions, metrics);
|
|
776
1155
|
}
|
|
777
1156
|
return sections.join("\n");
|
|
778
1157
|
}
|
|
779
|
-
const propertyId = state.manualPropertyId;
|
|
780
|
-
if (propertyId) {
|
|
781
|
-
sections.push(`### Property: ${propertyId}`, "");
|
|
782
|
-
const { dimensions, metrics } = await getMetadata(
|
|
783
|
-
rt.params,
|
|
784
|
-
propertyId
|
|
785
|
-
);
|
|
786
|
-
appendMetadataSection(sections, dimensions, metrics);
|
|
787
|
-
return sections.join("\n");
|
|
788
|
-
}
|
|
789
1158
|
sections.push(
|
|
790
|
-
"
|
|
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._",
|
|
791
1160
|
""
|
|
792
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 ?? "-");
|
|
1177
|
+
const timeZone = prop?.timeZone ?? "-";
|
|
1178
|
+
const currency = prop?.currencyCode ?? "-";
|
|
1179
|
+
const industry = escapePipe(prop?.industryCategory ?? "-");
|
|
1180
|
+
sections.push(
|
|
1181
|
+
`| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
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
|
+
}
|
|
793
1203
|
return sections.join("\n");
|
|
794
1204
|
}
|
|
795
1205
|
};
|
|
796
1206
|
|
|
797
1207
|
// ../connectors/src/connectors/google-analytics/tools/request.ts
|
|
798
1208
|
import { z } from "zod";
|
|
799
|
-
var BASE_URL2 = "https://analyticsdata.googleapis.com
|
|
1209
|
+
var BASE_URL2 = "https://analyticsdata.googleapis.com";
|
|
800
1210
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
801
1211
|
var inputSchema = z.object({
|
|
802
1212
|
toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
|
|
803
1213
|
connectionId: z.string().describe("ID of the Google Analytics connection to use"),
|
|
804
|
-
propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
|
|
805
1214
|
method: z.enum(["GET", "POST"]).describe("HTTP method"),
|
|
806
|
-
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'."),
|
|
807
1216
|
body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
|
|
808
1217
|
});
|
|
809
1218
|
var outputSchema = z.discriminatedUnion("success", [
|
|
@@ -820,11 +1229,10 @@ var outputSchema = z.discriminatedUnion("success", [
|
|
|
820
1229
|
var requestTool = new ConnectorTool({
|
|
821
1230
|
name: "request",
|
|
822
1231
|
description: `Send authenticated requests to the Google Analytics Data API.
|
|
823
|
-
Authentication is handled automatically using a service account
|
|
824
|
-
{propertyId} in the path is automatically replaced with the propertyId parameter.`,
|
|
1232
|
+
Authentication is handled automatically using a service account.`,
|
|
825
1233
|
inputSchema,
|
|
826
1234
|
outputSchema,
|
|
827
|
-
async execute({ connectionId,
|
|
1235
|
+
async execute({ connectionId, method, path: path2, body }, connections) {
|
|
828
1236
|
const connection2 = connections.find((c) => c.id === connectionId);
|
|
829
1237
|
if (!connection2) {
|
|
830
1238
|
return { success: false, error: `Connection ${connectionId} not found` };
|
|
@@ -844,8 +1252,7 @@ Authentication is handled automatically using a service account.
|
|
|
844
1252
|
if (!token) {
|
|
845
1253
|
return { success: false, error: "Failed to obtain access token" };
|
|
846
1254
|
}
|
|
847
|
-
const
|
|
848
|
-
const url = `${BASE_URL2}${resolvedPath}`;
|
|
1255
|
+
const url = `${BASE_URL2}${path2}`;
|
|
849
1256
|
const controller = new AbortController();
|
|
850
1257
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
851
1258
|
try {
|
|
@@ -892,18 +1299,20 @@ var googleAnalyticsConnector = new ConnectorPlugin({
|
|
|
892
1299
|
systemPrompt: {
|
|
893
1300
|
en: `### Tools
|
|
894
1301
|
|
|
895
|
-
- \`
|
|
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.
|
|
896
1303
|
|
|
897
1304
|
### Business Logic
|
|
898
1305
|
|
|
899
1306
|
The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
|
|
900
1307
|
|
|
901
1308
|
SDK methods (client created via \`connection(connectionId)\`):
|
|
902
|
-
- \`client.runReport(
|
|
903
|
-
- \`client.runRealtimeReport(
|
|
904
|
-
- \`client.getMetadata(
|
|
1309
|
+
- \`client.runReport(request)\` \u2014 run a GA4 report
|
|
1310
|
+
- \`client.runRealtimeReport(request)\` \u2014 run a realtime report
|
|
1311
|
+
- \`client.getMetadata(request)\` \u2014 fetch available dimensions/metrics
|
|
905
1312
|
- \`client.request(path, init?)\` \u2014 low-level authenticated fetch
|
|
906
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
|
+
|
|
907
1316
|
\`\`\`ts
|
|
908
1317
|
import type { Context } from "hono";
|
|
909
1318
|
import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
@@ -917,7 +1326,8 @@ export default async function handler(c: Context) {
|
|
|
917
1326
|
endDate?: string;
|
|
918
1327
|
}>();
|
|
919
1328
|
|
|
920
|
-
const { rows } = await ga.runReport(
|
|
1329
|
+
const { rows } = await ga.runReport({
|
|
1330
|
+
propertyId,
|
|
921
1331
|
dateRanges: [{ startDate, endDate }],
|
|
922
1332
|
dimensions: [{ name: "date" }],
|
|
923
1333
|
metrics: [{ name: "activeUsers" }, { name: "sessions" }],
|
|
@@ -938,8 +1348,8 @@ export default async function handler(c: Context) {
|
|
|
938
1348
|
|
|
939
1349
|
Common operations:
|
|
940
1350
|
|
|
941
|
-
- **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
|
|
942
|
-
- **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:
|
|
943
1353
|
\`\`\`json
|
|
944
1354
|
{
|
|
945
1355
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -948,7 +1358,7 @@ Common operations:
|
|
|
948
1358
|
"limit": 100
|
|
949
1359
|
}
|
|
950
1360
|
\`\`\`
|
|
951
|
-
- **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
|
|
1361
|
+
- **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
|
|
952
1362
|
|
|
953
1363
|
#### Common dimensions
|
|
954
1364
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -961,18 +1371,20 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
961
1371
|
- Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
|
|
962
1372
|
ja: `### \u30C4\u30FC\u30EB
|
|
963
1373
|
|
|
964
|
-
- \`
|
|
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
|
|
965
1375
|
|
|
966
1376
|
### Business Logic
|
|
967
1377
|
|
|
968
1378
|
\u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
|
|
969
1379
|
|
|
970
1380
|
SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
|
|
971
|
-
- \`client.runReport(
|
|
972
|
-
- \`client.runRealtimeReport(
|
|
973
|
-
- \`client.getMetadata(
|
|
1381
|
+
- \`client.runReport(request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
1382
|
+
- \`client.runRealtimeReport(request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
|
|
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
|
|
974
1384
|
- \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
|
|
975
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
|
+
|
|
976
1388
|
\`\`\`ts
|
|
977
1389
|
import type { Context } from "hono";
|
|
978
1390
|
import { connection } from "@squadbase/vite-server/connectors/google-analytics";
|
|
@@ -986,7 +1398,8 @@ export default async function handler(c: Context) {
|
|
|
986
1398
|
endDate?: string;
|
|
987
1399
|
}>();
|
|
988
1400
|
|
|
989
|
-
const { rows } = await ga.runReport(
|
|
1401
|
+
const { rows } = await ga.runReport({
|
|
1402
|
+
propertyId,
|
|
990
1403
|
dateRanges: [{ startDate, endDate }],
|
|
991
1404
|
dimensions: [{ name: "date" }],
|
|
992
1405
|
metrics: [{ name: "activeUsers" }, { name: "sessions" }],
|
|
@@ -1007,8 +1420,8 @@ export default async function handler(c: Context) {
|
|
|
1007
1420
|
|
|
1008
1421
|
\u4E3B\u306A\u64CD\u4F5C:
|
|
1009
1422
|
|
|
1010
|
-
- **\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\`
|
|
1011
|
-
- **\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:
|
|
1012
1425
|
\`\`\`json
|
|
1013
1426
|
{
|
|
1014
1427
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -1017,7 +1430,7 @@ export default async function handler(c: Context) {
|
|
|
1017
1430
|
"limit": 100
|
|
1018
1431
|
}
|
|
1019
1432
|
\`\`\`
|
|
1020
|
-
- **\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\`
|
|
1021
1434
|
|
|
1022
1435
|
#### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
|
|
1023
1436
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -1039,8 +1452,22 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
1039
1452
|
error: "google-analytics: missing service account key"
|
|
1040
1453
|
};
|
|
1041
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
|
+
}
|
|
1042
1466
|
try {
|
|
1043
|
-
const res = await dataFetch(
|
|
1467
|
+
const res = await dataFetch(
|
|
1468
|
+
params,
|
|
1469
|
+
`/properties/${propertyId}/metadata`
|
|
1470
|
+
);
|
|
1044
1471
|
if (!res.ok) {
|
|
1045
1472
|
const body = await res.text().catch(() => res.statusText);
|
|
1046
1473
|
return {
|