@squadbase/vite-server 0.1.17-dev.a9ddcfa → 0.1.17-dev.e4768d1
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 +8941 -3700
- package/dist/connectors/airtable-oauth.js +87 -23
- package/dist/connectors/airtable.js +130 -23
- package/dist/connectors/amplitude.js +47 -23
- package/dist/connectors/anthropic.js +41 -17
- package/dist/connectors/asana.js +76 -25
- package/dist/connectors/attio.js +69 -28
- package/dist/connectors/aws-billing.js +47 -23
- package/dist/connectors/azure-sql.js +322 -25
- package/dist/connectors/backlog-api-key.js +79 -30
- package/dist/connectors/clickup.js +89 -25
- package/dist/connectors/cosmosdb.js +51 -27
- package/dist/connectors/customerio.js +47 -23
- package/dist/connectors/dbt.js +725 -40
- package/dist/connectors/freshdesk.js +121 -23
- package/dist/connectors/freshsales.js +47 -23
- package/dist/connectors/freshservice.js +47 -23
- package/dist/connectors/gamma.js +54 -30
- package/dist/connectors/gemini.js +41 -17
- package/dist/connectors/github.js +51 -27
- package/dist/connectors/gmail-oauth.js +42 -23
- package/dist/connectors/gmail.js +43 -19
- package/dist/connectors/google-ads.js +47 -23
- package/dist/connectors/google-analytics-oauth.js +191 -40
- package/dist/connectors/google-analytics.js +529 -111
- package/dist/connectors/google-audit-log.js +43 -19
- package/dist/connectors/google-calendar-oauth.js +100 -30
- package/dist/connectors/google-calendar.js +100 -26
- package/dist/connectors/google-docs.js +42 -23
- package/dist/connectors/google-drive.js +64 -23
- package/dist/connectors/google-search-console-oauth.js +165 -32
- package/dist/connectors/google-sheets.js +142 -199
- package/dist/connectors/google-slides.js +42 -23
- package/dist/connectors/grafana.js +84 -25
- package/dist/connectors/hackernews.d.ts +5 -0
- package/dist/connectors/hackernews.js +909 -0
- package/dist/connectors/hubspot-oauth.js +73 -22
- package/dist/connectors/hubspot.js +64 -24
- package/dist/connectors/influxdb.js +47 -23
- package/dist/connectors/intercom-oauth.js +104 -25
- package/dist/connectors/intercom.js +51 -27
- package/dist/connectors/jdbc.js +76 -25
- package/dist/connectors/jira-api-key.js +107 -26
- package/dist/connectors/kintone-api-token.js +105 -33
- package/dist/connectors/kintone.js +182 -26
- package/dist/connectors/linear.js +93 -27
- package/dist/connectors/linkedin-ads.js +80 -29
- package/dist/connectors/mailchimp-oauth.js +45 -21
- package/dist/connectors/mailchimp.js +45 -21
- package/dist/connectors/meta-ads-oauth.js +72 -29
- package/dist/connectors/meta-ads.js +74 -29
- package/dist/connectors/mixpanel.js +47 -23
- package/dist/connectors/monday.js +48 -24
- package/dist/connectors/mongodb.js +47 -23
- package/dist/connectors/notion-oauth.js +92 -24
- package/dist/connectors/notion.js +144 -26
- package/dist/connectors/openai.js +41 -17
- package/dist/connectors/oracle.js +316 -26
- package/dist/connectors/outlook-oauth.js +53 -34
- package/dist/connectors/powerbi-oauth.js +45 -26
- package/dist/connectors/salesforce.js +150 -24
- package/dist/connectors/semrush.js +45 -21
- package/dist/connectors/sentry.js +75 -25
- package/dist/connectors/shopify-oauth.js +75 -23
- package/dist/connectors/shopify.js +47 -23
- package/dist/connectors/sqlserver.js +322 -25
- package/dist/connectors/stripe-api-key.js +105 -30
- package/dist/connectors/stripe-oauth.js +102 -32
- package/dist/connectors/supabase.js +315 -21
- package/dist/connectors/tableau.js +54 -30
- package/dist/connectors/tiktok-ads.js +76 -31
- package/dist/connectors/wix-store.js +47 -23
- package/dist/connectors/x.d.ts +5 -0
- package/dist/connectors/x.js +951 -0
- package/dist/connectors/zendesk-oauth.js +87 -25
- package/dist/connectors/zendesk.js +51 -27
- package/dist/index.js +9005 -3709
- package/dist/main.js +9005 -3709
- package/dist/vite-plugin.js +8939 -3698
- package/package.json +12 -4
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
-
var __esm = (fn, res) => function __init() {
|
|
3
|
-
|
|
2
|
+
var __esm = (fn, res, err) => function __init() {
|
|
3
|
+
if (err) throw err[0];
|
|
4
|
+
try {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
} catch (e) {
|
|
7
|
+
throw err = [e], e;
|
|
8
|
+
}
|
|
4
9
|
};
|
|
5
10
|
|
|
6
11
|
// ../connectors/src/parameter-definition.ts
|
|
@@ -311,6 +316,15 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
311
316
|
* `runSetupFlow` from `setup-flow.ts`.
|
|
312
317
|
*/
|
|
313
318
|
setup;
|
|
319
|
+
/**
|
|
320
|
+
* 統一 introspection。SQL DB の `information_schema` / `SHOW` / `DESCRIBE` や SaaS の
|
|
321
|
+
* メタデータ API といった取得経路の差を吸収し、tables / columns / PK / FK を共通型
|
|
322
|
+
* {@link IntrospectedSchema} へ正規化して返す。取得経路は {@link IntrospectContext} で
|
|
323
|
+
* 抽象化され、SQL DB は `ctx.query`、SaaS は `ctx.proxyFetch` + `ctx.params` を使う。
|
|
324
|
+
* `ctx.profile === true` のときは L2 プロファイル(行数・NULL 率等)も best-effort で埋める。
|
|
325
|
+
* coding-agent の `DataContextIndex` がデータ地図を構築する際に使う。
|
|
326
|
+
*/
|
|
327
|
+
introspect;
|
|
314
328
|
/**
|
|
315
329
|
* Opt-out of the default "verify before save" behavior on connection
|
|
316
330
|
* creation. The backend invokes `checkConnection` synchronously while
|
|
@@ -325,6 +339,14 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
325
339
|
* the default verify-on-create behavior without opt-in.
|
|
326
340
|
*/
|
|
327
341
|
skipConnectionCheckOnCreate;
|
|
342
|
+
/**
|
|
343
|
+
* 決定論的な「立ち上げ survey」レシピ(ルールベース prewarm 基盤)。実装した connector は、LLM を使わずに
|
|
344
|
+
* read-only で自分を survey する probe 列(テーブル/列メタデータ → 小サンプル)を宣言する。coding-agent の
|
|
345
|
+
* recon driver がこれを読み、connector ツールの `execute` を直接呼んで結果を seed にする。未実装の connector は
|
|
346
|
+
* driver 側で safely skip される(その connection は listConnections のみ seed)。標準 `information_schema` を
|
|
347
|
+
* 持つ SQL connector は `makeStandardSqlSurveyProbes` を使える。**probe は必ず read-only**。
|
|
348
|
+
*/
|
|
349
|
+
surveyProbes;
|
|
328
350
|
constructor(config) {
|
|
329
351
|
this.slug = config.slug;
|
|
330
352
|
this.authType = config.authType;
|
|
@@ -342,7 +364,9 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
342
364
|
this.query = config.query;
|
|
343
365
|
this.checkConnection = config.checkConnection;
|
|
344
366
|
this.setup = config.setup;
|
|
367
|
+
this.introspect = config.introspect;
|
|
345
368
|
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
369
|
+
this.surveyProbes = config.surveyProbes;
|
|
346
370
|
}
|
|
347
371
|
get connectorKey() {
|
|
348
372
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -350,7 +374,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
350
374
|
/**
|
|
351
375
|
* Create tools for connections that belong to this connector.
|
|
352
376
|
* Filters connections by connectorKey internally.
|
|
353
|
-
* Returns tools keyed as
|
|
377
|
+
* Returns tools keyed as `connector_${connectorKey}_${toolName}`.
|
|
354
378
|
*/
|
|
355
379
|
createTools(connections, config, opts) {
|
|
356
380
|
const myConnections = connections.filter(
|
|
@@ -360,7 +384,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
360
384
|
for (const t of Object.values(this.tools)) {
|
|
361
385
|
const tool = t.createTool(myConnections, config);
|
|
362
386
|
const originalToModelOutput = tool.toModelOutput;
|
|
363
|
-
result[
|
|
387
|
+
result[`connector_${this.connectorKey}_${t.name}`] = {
|
|
364
388
|
...tool,
|
|
365
389
|
toModelOutput: async (options) => {
|
|
366
390
|
if (!originalToModelOutput) {
|
|
@@ -380,25 +404,25 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
380
404
|
if (authType) return `${slug}-${authType}`;
|
|
381
405
|
const LEGACY_NULL_AUTH_TYPE_MAP = {
|
|
382
406
|
// user-password
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
407
|
+
postgresql: "user-password",
|
|
408
|
+
mysql: "user-password",
|
|
409
|
+
clickhouse: "user-password",
|
|
410
|
+
kintone: "user-password",
|
|
387
411
|
"squadbase-db": "user-password",
|
|
388
412
|
// service-account
|
|
389
|
-
|
|
390
|
-
|
|
413
|
+
snowflake: "service-account",
|
|
414
|
+
bigquery: "service-account",
|
|
391
415
|
"google-analytics": "service-account",
|
|
392
416
|
"google-calendar": "service-account",
|
|
393
417
|
"aws-athena": "service-account",
|
|
394
|
-
|
|
418
|
+
redshift: "service-account",
|
|
395
419
|
// api-key
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
420
|
+
databricks: "api-key",
|
|
421
|
+
dbt: "api-key",
|
|
422
|
+
airtable: "api-key",
|
|
423
|
+
openai: "api-key",
|
|
424
|
+
gemini: "api-key",
|
|
425
|
+
anthropic: "api-key",
|
|
402
426
|
"wix-store": "api-key"
|
|
403
427
|
};
|
|
404
428
|
const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
|
|
@@ -490,10 +514,10 @@ var AUTH_TYPES = {
|
|
|
490
514
|
// ../connectors/src/connectors/google-analytics/setup.ts
|
|
491
515
|
var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
492
516
|
dataOverviewInstructions: {
|
|
493
|
-
en: `1. Call
|
|
494
|
-
2. Call
|
|
495
|
-
ja: `1.
|
|
496
|
-
2.
|
|
517
|
+
en: `1. Call connector_google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
|
|
518
|
+
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`,
|
|
519
|
+
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
|
|
520
|
+
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`
|
|
497
521
|
}
|
|
498
522
|
});
|
|
499
523
|
|
|
@@ -601,7 +625,17 @@ async function dataFetch(params, path2, init) {
|
|
|
601
625
|
// ../connectors/src/connectors/google-analytics/setup-flow.ts
|
|
602
626
|
var ALL_PROPERTIES = "__ALL_PROPERTIES__";
|
|
603
627
|
var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
|
|
604
|
-
var
|
|
628
|
+
var MAX_PROPERTIES_TO_EXPLORE = 2;
|
|
629
|
+
var TOP_N_SOURCES = 8;
|
|
630
|
+
var TOP_N_COUNTRIES = 8;
|
|
631
|
+
var TOP_N_BROWSERS = 5;
|
|
632
|
+
var TOP_N_PAGES = 10;
|
|
633
|
+
var TOP_N_EVENTS = 10;
|
|
634
|
+
var STANDARD_METADATA_LIMIT = 25;
|
|
635
|
+
var CUSTOM_METADATA_LIMIT = 15;
|
|
636
|
+
var DESCRIPTION_MAX = 140;
|
|
637
|
+
var PAGE_PATH_MAX = 80;
|
|
638
|
+
var EVENT_NAME_MAX = 60;
|
|
605
639
|
async function listAccountSummaries(params) {
|
|
606
640
|
const summaries = [];
|
|
607
641
|
let pageToken;
|
|
@@ -624,9 +658,13 @@ function propertyIdFromResource(resource) {
|
|
|
624
658
|
return (resource ?? "").replace(/^properties\//, "");
|
|
625
659
|
}
|
|
626
660
|
async function getProperty(params, propertyId) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
661
|
+
try {
|
|
662
|
+
const res = await adminFetch(params, `/properties/${propertyId}`);
|
|
663
|
+
if (!res.ok) return null;
|
|
664
|
+
return await res.json();
|
|
665
|
+
} catch {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
630
668
|
}
|
|
631
669
|
async function getMetadata(params, propertyId) {
|
|
632
670
|
try {
|
|
@@ -634,41 +672,387 @@ async function getMetadata(params, propertyId) {
|
|
|
634
672
|
params,
|
|
635
673
|
`/properties/${propertyId}/metadata`
|
|
636
674
|
);
|
|
637
|
-
if (!res.ok) return
|
|
638
|
-
|
|
675
|
+
if (!res.ok) return null;
|
|
676
|
+
return await res.json();
|
|
677
|
+
} catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
async function probePropertyAccess(params, propertyId) {
|
|
682
|
+
try {
|
|
683
|
+
const res = await dataFetch(
|
|
684
|
+
params,
|
|
685
|
+
`/properties/${propertyId}:runReport`,
|
|
686
|
+
{
|
|
687
|
+
method: "POST",
|
|
688
|
+
headers: { "Content-Type": "application/json" },
|
|
689
|
+
body: JSON.stringify({
|
|
690
|
+
dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
|
|
691
|
+
metrics: [{ name: "activeUsers" }],
|
|
692
|
+
limit: 1
|
|
693
|
+
})
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
if (res.ok) return { ok: true };
|
|
697
|
+
const body = await res.text().catch(() => res.statusText);
|
|
698
|
+
return { ok: false, status: res.status, body };
|
|
699
|
+
} catch (err) {
|
|
639
700
|
return {
|
|
640
|
-
|
|
641
|
-
|
|
701
|
+
ok: false,
|
|
702
|
+
status: "network",
|
|
703
|
+
body: err instanceof Error ? err.message : String(err)
|
|
642
704
|
};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async function batchRunReports(params, propertyId, requests) {
|
|
708
|
+
try {
|
|
709
|
+
const res = await dataFetch(
|
|
710
|
+
params,
|
|
711
|
+
`/properties/${propertyId}:batchRunReports`,
|
|
712
|
+
{
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({ requests })
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
if (!res.ok) return requests.map(() => ({}));
|
|
719
|
+
const data = await res.json();
|
|
720
|
+
const reports = data.reports ?? [];
|
|
721
|
+
return requests.map((_, i) => reports[i] ?? {});
|
|
643
722
|
} catch {
|
|
644
|
-
return
|
|
723
|
+
return requests.map(() => ({}));
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
var LAST_30D = { startDate: "30daysAgo", endDate: "today" };
|
|
727
|
+
var PREV_30D = { startDate: "60daysAgo", endDate: "31daysAgo" };
|
|
728
|
+
var HEADLINE_METRICS = [
|
|
729
|
+
"activeUsers",
|
|
730
|
+
"newUsers",
|
|
731
|
+
"sessions",
|
|
732
|
+
"screenPageViews",
|
|
733
|
+
"engagementRate",
|
|
734
|
+
"averageSessionDuration",
|
|
735
|
+
"keyEvents",
|
|
736
|
+
"totalRevenue"
|
|
737
|
+
];
|
|
738
|
+
function buildHeadlineRequest() {
|
|
739
|
+
return {
|
|
740
|
+
dateRanges: [
|
|
741
|
+
{ ...LAST_30D, name: "current" },
|
|
742
|
+
{ ...PREV_30D, name: "previous" }
|
|
743
|
+
],
|
|
744
|
+
metrics: HEADLINE_METRICS.map((name) => ({ name }))
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function buildTopDimensionRequest(dimension, metric, limit) {
|
|
748
|
+
return {
|
|
749
|
+
dateRanges: [LAST_30D],
|
|
750
|
+
dimensions: [{ name: dimension }],
|
|
751
|
+
metrics: [{ name: metric }],
|
|
752
|
+
limit,
|
|
753
|
+
orderBys: [{ desc: true, metric: { metricName: metric } }]
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function buildTopPagesRequest() {
|
|
757
|
+
return {
|
|
758
|
+
dateRanges: [LAST_30D],
|
|
759
|
+
dimensions: [{ name: "pagePath" }],
|
|
760
|
+
metrics: [
|
|
761
|
+
{ name: "screenPageViews" },
|
|
762
|
+
{ name: "activeUsers" },
|
|
763
|
+
{ name: "averageSessionDuration" }
|
|
764
|
+
],
|
|
765
|
+
limit: TOP_N_PAGES,
|
|
766
|
+
orderBys: [{ desc: true, metric: { metricName: "screenPageViews" } }]
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function buildTopEventsRequest() {
|
|
770
|
+
return {
|
|
771
|
+
dateRanges: [LAST_30D],
|
|
772
|
+
dimensions: [{ name: "eventName" }],
|
|
773
|
+
metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
|
|
774
|
+
limit: TOP_N_EVENTS,
|
|
775
|
+
orderBys: [{ desc: true, metric: { metricName: "eventCount" } }]
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function escapePipe(s) {
|
|
779
|
+
return s.replace(/\|/g, "\\|");
|
|
780
|
+
}
|
|
781
|
+
function truncate(s, max) {
|
|
782
|
+
if (!s) return "";
|
|
783
|
+
if (s.length <= max) return s;
|
|
784
|
+
return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
785
|
+
}
|
|
786
|
+
function formatNumber(raw) {
|
|
787
|
+
if (raw == null || raw === "") return "-";
|
|
788
|
+
const n = Number(raw);
|
|
789
|
+
if (!Number.isFinite(n)) return raw;
|
|
790
|
+
if (Number.isInteger(n)) return n.toLocaleString("en-US");
|
|
791
|
+
return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
792
|
+
}
|
|
793
|
+
function formatPercent(raw) {
|
|
794
|
+
if (raw == null || raw === "") return "-";
|
|
795
|
+
const n = Number(raw);
|
|
796
|
+
if (!Number.isFinite(n)) return raw;
|
|
797
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
798
|
+
}
|
|
799
|
+
function formatDuration(rawSeconds) {
|
|
800
|
+
if (rawSeconds == null || rawSeconds === "") return "-";
|
|
801
|
+
const s = Math.round(Number(rawSeconds));
|
|
802
|
+
if (!Number.isFinite(s)) return rawSeconds;
|
|
803
|
+
if (s < 60) return `${s}s`;
|
|
804
|
+
const mins = Math.floor(s / 60);
|
|
805
|
+
const secs = s % 60;
|
|
806
|
+
return `${mins}m ${secs}s`;
|
|
807
|
+
}
|
|
808
|
+
function deltaArrow(curr, prev) {
|
|
809
|
+
if (!Number.isFinite(prev) || prev === 0) return "";
|
|
810
|
+
const pct = (curr - prev) / prev * 100;
|
|
811
|
+
if (Math.abs(pct) < 0.5) return " (\u2248 flat)";
|
|
812
|
+
const sign = pct > 0 ? "\u2191" : "\u2193";
|
|
813
|
+
return ` (${sign} ${Math.abs(pct).toFixed(1)}% vs prior 30d)`;
|
|
814
|
+
}
|
|
815
|
+
function pivotComparisonReport(report) {
|
|
816
|
+
const out = /* @__PURE__ */ new Map();
|
|
817
|
+
const headers = report.metricHeaders ?? [];
|
|
818
|
+
const rows = report.rows ?? [];
|
|
819
|
+
for (let i = 0; i < headers.length; i++) {
|
|
820
|
+
const name = headers[i]?.name;
|
|
821
|
+
if (!name) continue;
|
|
822
|
+
out.set(name, {
|
|
823
|
+
current: Number(rows[0]?.metricValues?.[i]?.value ?? "0"),
|
|
824
|
+
previous: Number(rows[1]?.metricValues?.[i]?.value ?? "0")
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return out;
|
|
828
|
+
}
|
|
829
|
+
function renderHeadline(sections, report) {
|
|
830
|
+
const m = pivotComparisonReport(report);
|
|
831
|
+
if (m.size === 0) return;
|
|
832
|
+
sections.push("#### Headline metrics (last 30 days)", "");
|
|
833
|
+
const lines = [];
|
|
834
|
+
const push = (label, key, formatter) => {
|
|
835
|
+
const cell = m.get(key);
|
|
836
|
+
if (!cell) return;
|
|
837
|
+
const formatted = formatter(String(cell.current));
|
|
838
|
+
const delta = deltaArrow(cell.current, cell.previous);
|
|
839
|
+
lines.push([label, `${formatted}${delta}`]);
|
|
840
|
+
};
|
|
841
|
+
push("Active users", "activeUsers", formatNumber);
|
|
842
|
+
push("New users", "newUsers", formatNumber);
|
|
843
|
+
push("Sessions", "sessions", formatNumber);
|
|
844
|
+
push("Page / screen views", "screenPageViews", formatNumber);
|
|
845
|
+
push("Engagement rate", "engagementRate", formatPercent);
|
|
846
|
+
push("Avg session duration", "averageSessionDuration", formatDuration);
|
|
847
|
+
push("Key events (conversions)", "keyEvents", formatNumber);
|
|
848
|
+
const rev = m.get("totalRevenue");
|
|
849
|
+
if (rev && rev.current > 0) {
|
|
850
|
+
lines.push([
|
|
851
|
+
"Total revenue",
|
|
852
|
+
`${formatNumber(String(rev.current))}${deltaArrow(rev.current, rev.previous)}`
|
|
853
|
+
]);
|
|
854
|
+
}
|
|
855
|
+
for (const [k, v] of lines) sections.push(`- **${k}**: ${v}`);
|
|
856
|
+
sections.push("");
|
|
857
|
+
}
|
|
858
|
+
function renderBreakdown(sections, title, blurb, report, topN, metricFormatter = formatNumber) {
|
|
859
|
+
const rows = report.rows ?? [];
|
|
860
|
+
if (rows.length === 0) return;
|
|
861
|
+
const dim = report.dimensionHeaders?.[0]?.name ?? "dimension";
|
|
862
|
+
const metricName = report.metricHeaders?.[0]?.name ?? "metric";
|
|
863
|
+
sections.push(`#### ${title}`, "", blurb, "");
|
|
864
|
+
for (const row of rows.slice(0, topN)) {
|
|
865
|
+
const label = row.dimensionValues?.[0]?.value || `(empty ${dim})`;
|
|
866
|
+
const value = metricFormatter(row.metricValues?.[0]?.value);
|
|
867
|
+
sections.push(`- ${label}: ${value} ${metricName}`);
|
|
868
|
+
}
|
|
869
|
+
sections.push("");
|
|
870
|
+
}
|
|
871
|
+
function renderTopPages(sections, report) {
|
|
872
|
+
const rows = report.rows ?? [];
|
|
873
|
+
if (rows.length === 0) return;
|
|
874
|
+
sections.push(
|
|
875
|
+
`#### Top pages by views (last 30 days)`,
|
|
876
|
+
"",
|
|
877
|
+
"_The most-visited pages \u2014 strong candidates for content-performance dashboards._",
|
|
878
|
+
""
|
|
879
|
+
);
|
|
880
|
+
sections.push("| Path | Page views | Active users | Avg session duration |");
|
|
881
|
+
sections.push("|------|------------|--------------|----------------------|");
|
|
882
|
+
for (const row of rows.slice(0, TOP_N_PAGES)) {
|
|
883
|
+
const path2 = truncate(row.dimensionValues?.[0]?.value, PAGE_PATH_MAX) || "-";
|
|
884
|
+
sections.push(
|
|
885
|
+
`| ${escapePipe(path2)} | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
|
|
886
|
+
row.metricValues?.[1]?.value
|
|
887
|
+
)} | ${formatDuration(row.metricValues?.[2]?.value)} |`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
sections.push("");
|
|
891
|
+
}
|
|
892
|
+
function renderTopEvents(sections, report) {
|
|
893
|
+
const rows = report.rows ?? [];
|
|
894
|
+
if (rows.length === 0) return;
|
|
895
|
+
sections.push(
|
|
896
|
+
`#### Top events (last 30 days)`,
|
|
897
|
+
"",
|
|
898
|
+
"_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._",
|
|
899
|
+
""
|
|
900
|
+
);
|
|
901
|
+
sections.push("| Event | Event count | Unique users |");
|
|
902
|
+
sections.push("|-------|-------------|--------------|");
|
|
903
|
+
for (const row of rows.slice(0, TOP_N_EVENTS)) {
|
|
904
|
+
const name = truncate(row.dimensionValues?.[0]?.value, EVENT_NAME_MAX) || "-";
|
|
905
|
+
sections.push(
|
|
906
|
+
`| \`${escapePipe(name)}\` | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
|
|
907
|
+
row.metricValues?.[1]?.value
|
|
908
|
+
)} |`
|
|
909
|
+
);
|
|
645
910
|
}
|
|
911
|
+
sections.push("");
|
|
646
912
|
}
|
|
647
|
-
function
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
913
|
+
function renderMetadata(sections, metadata) {
|
|
914
|
+
const dims = metadata.dimensions ?? [];
|
|
915
|
+
const mets = metadata.metrics ?? [];
|
|
916
|
+
if (dims.length === 0 && mets.length === 0) return;
|
|
917
|
+
const customDims = dims.filter((d) => d.customDefinition);
|
|
918
|
+
const customMets = mets.filter((m) => m.customDefinition);
|
|
919
|
+
const standardDims = dims.filter((d) => !d.customDefinition);
|
|
920
|
+
const standardMets = mets.filter((m) => !m.customDefinition);
|
|
921
|
+
sections.push("#### Available query surface", "");
|
|
922
|
+
sections.push(
|
|
923
|
+
"_Use these in custom `runReport` calls. Custom definitions reveal what the team specifically tracks beyond GA4's defaults._",
|
|
924
|
+
""
|
|
925
|
+
);
|
|
926
|
+
if (customDims.length > 0 || customMets.length > 0) {
|
|
927
|
+
sections.push(`##### Custom definitions`, "");
|
|
928
|
+
for (const d of customDims.slice(0, CUSTOM_METADATA_LIMIT)) {
|
|
929
|
+
const desc = d.description ? ` \u2014 ${truncate(d.description, DESCRIPTION_MAX)}` : "";
|
|
930
|
+
sections.push(`- dimension \`${d.apiName ?? d.uiName}\`${desc}`);
|
|
652
931
|
}
|
|
653
|
-
if (
|
|
932
|
+
if (customDims.length > CUSTOM_METADATA_LIMIT) {
|
|
654
933
|
sections.push(
|
|
655
|
-
`- \u2026and ${
|
|
934
|
+
`- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
|
|
656
935
|
);
|
|
657
936
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
sections.push(`#### Metrics (${metrics.length})`, "");
|
|
662
|
-
for (const m of metrics.slice(0, METADATA_DISPLAY_LIMIT)) {
|
|
663
|
-
sections.push(`- ${m.apiName ?? m.uiName ?? "(unknown)"}`);
|
|
937
|
+
for (const m of customMets.slice(0, CUSTOM_METADATA_LIMIT)) {
|
|
938
|
+
const desc = m.description ? ` \u2014 ${truncate(m.description, DESCRIPTION_MAX)}` : "";
|
|
939
|
+
sections.push(`- metric \`${m.apiName ?? m.uiName}\`${desc}`);
|
|
664
940
|
}
|
|
665
|
-
if (
|
|
941
|
+
if (customMets.length > CUSTOM_METADATA_LIMIT) {
|
|
666
942
|
sections.push(
|
|
667
|
-
`- \u2026and ${
|
|
943
|
+
`- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
|
|
668
944
|
);
|
|
669
945
|
}
|
|
670
946
|
sections.push("");
|
|
671
947
|
}
|
|
948
|
+
if (standardDims.length > 0) {
|
|
949
|
+
sections.push(`##### Standard dimensions (${standardDims.length})`);
|
|
950
|
+
const names = standardDims.slice(0, STANDARD_METADATA_LIMIT).map((d) => `\`${d.apiName ?? d.uiName}\``).join(", ");
|
|
951
|
+
const overflow = standardDims.length > STANDARD_METADATA_LIMIT ? ` _(+${standardDims.length - STANDARD_METADATA_LIMIT} more)_` : "";
|
|
952
|
+
sections.push(names + overflow, "");
|
|
953
|
+
}
|
|
954
|
+
if (standardMets.length > 0) {
|
|
955
|
+
sections.push(`##### Standard metrics (${standardMets.length})`);
|
|
956
|
+
const names = standardMets.slice(0, STANDARD_METADATA_LIMIT).map((m) => `\`${m.apiName ?? m.uiName}\``).join(", ");
|
|
957
|
+
const overflow = standardMets.length > STANDARD_METADATA_LIMIT ? ` _(+${standardMets.length - STANDARD_METADATA_LIMIT} more)_` : "";
|
|
958
|
+
sections.push(names + overflow, "");
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function renderProbeFailure(sections, propertyId, probe, serviceAccountEmail) {
|
|
962
|
+
sections.push(`#### Connection probe failed`, "");
|
|
963
|
+
if (probe.status === 403) {
|
|
964
|
+
sections.push(
|
|
965
|
+
`_The service account could not access GA4 property \`${propertyId}\`._`,
|
|
966
|
+
"",
|
|
967
|
+
`**Fix**: In GA4 (Admin > Property access management), grant **Viewer** role to the service account below, then re-run setup.`,
|
|
968
|
+
""
|
|
969
|
+
);
|
|
970
|
+
if (serviceAccountEmail) {
|
|
971
|
+
sections.push(`\`${serviceAccountEmail}\``, "");
|
|
972
|
+
}
|
|
973
|
+
} else if (probe.status === 404) {
|
|
974
|
+
sections.push(
|
|
975
|
+
`_GA4 property \`${propertyId}\` was not found. Double-check the Property ID in **Admin > Property Settings**._`,
|
|
976
|
+
""
|
|
977
|
+
);
|
|
978
|
+
} else if (probe.status === "network") {
|
|
979
|
+
sections.push(
|
|
980
|
+
`_Could not reach the GA4 Data API: ${probe.body}_`,
|
|
981
|
+
""
|
|
982
|
+
);
|
|
983
|
+
} else {
|
|
984
|
+
sections.push(
|
|
985
|
+
`_GA4 Data API returned ${probe.status}: ${probe.body.slice(0, 400)}_`,
|
|
986
|
+
""
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
async function exploreProperty(sections, params, propertyId, serviceAccountEmail) {
|
|
991
|
+
const probe = await probePropertyAccess(params, propertyId);
|
|
992
|
+
if (!probe.ok) {
|
|
993
|
+
renderProbeFailure(sections, propertyId, probe, serviceAccountEmail);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const batch1Requests = [
|
|
997
|
+
buildHeadlineRequest(),
|
|
998
|
+
buildTopDimensionRequest("sessionSourceMedium", "sessions", TOP_N_SOURCES),
|
|
999
|
+
buildTopDimensionRequest("country", "activeUsers", TOP_N_COUNTRIES),
|
|
1000
|
+
buildTopDimensionRequest("deviceCategory", "sessions", 5),
|
|
1001
|
+
buildTopDimensionRequest("browser", "activeUsers", TOP_N_BROWSERS)
|
|
1002
|
+
];
|
|
1003
|
+
const batch2Requests = [buildTopPagesRequest(), buildTopEventsRequest()];
|
|
1004
|
+
const [batch1, batch2, metadata] = await Promise.all([
|
|
1005
|
+
batchRunReports(params, propertyId, batch1Requests),
|
|
1006
|
+
batchRunReports(params, propertyId, batch2Requests),
|
|
1007
|
+
getMetadata(params, propertyId)
|
|
1008
|
+
]);
|
|
1009
|
+
const [headline, sourceMedium, country, device, browser] = batch1;
|
|
1010
|
+
const [topPages, topEvents] = batch2;
|
|
1011
|
+
if (headline) renderHeadline(sections, headline);
|
|
1012
|
+
const hasTrafficSignal = (sourceMedium?.rows?.length ?? 0) + (country?.rows?.length ?? 0) + (device?.rows?.length ?? 0) + (browser?.rows?.length ?? 0) > 0;
|
|
1013
|
+
if (hasTrafficSignal) {
|
|
1014
|
+
sections.push(
|
|
1015
|
+
"#### Traffic mix (last 30 days)",
|
|
1016
|
+
"",
|
|
1017
|
+
"_Where users are coming from \u2014 useful for dashboards that segment by acquisition channel, geography, or device._",
|
|
1018
|
+
""
|
|
1019
|
+
);
|
|
1020
|
+
if (sourceMedium)
|
|
1021
|
+
renderBreakdown(
|
|
1022
|
+
sections,
|
|
1023
|
+
"Top source / medium",
|
|
1024
|
+
"_Acquisition channels driving sessions._",
|
|
1025
|
+
sourceMedium,
|
|
1026
|
+
TOP_N_SOURCES
|
|
1027
|
+
);
|
|
1028
|
+
if (country)
|
|
1029
|
+
renderBreakdown(
|
|
1030
|
+
sections,
|
|
1031
|
+
"Top countries by active users",
|
|
1032
|
+
"_Geography \u2014 informs which date/time filters and locale segments to expose._",
|
|
1033
|
+
country,
|
|
1034
|
+
TOP_N_COUNTRIES
|
|
1035
|
+
);
|
|
1036
|
+
if (device)
|
|
1037
|
+
renderBreakdown(
|
|
1038
|
+
sections,
|
|
1039
|
+
"Device categories",
|
|
1040
|
+
"_Desktop / mobile / tablet split \u2014 informs responsive cuts._",
|
|
1041
|
+
device,
|
|
1042
|
+
5
|
|
1043
|
+
);
|
|
1044
|
+
if (browser)
|
|
1045
|
+
renderBreakdown(
|
|
1046
|
+
sections,
|
|
1047
|
+
"Top browsers",
|
|
1048
|
+
"_Browser distribution \u2014 informs compatibility dashboards or QA filters._",
|
|
1049
|
+
browser,
|
|
1050
|
+
TOP_N_BROWSERS
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
if (topPages) renderTopPages(sections, topPages);
|
|
1054
|
+
if (topEvents) renderTopEvents(sections, topEvents);
|
|
1055
|
+
if (metadata) renderMetadata(sections, metadata);
|
|
672
1056
|
}
|
|
673
1057
|
var googleAnalyticsSetupFlow = {
|
|
674
1058
|
initialState: () => ({}),
|
|
@@ -737,8 +1121,8 @@ var googleAnalyticsSetupFlow = {
|
|
|
737
1121
|
slug: "manualPropertyId",
|
|
738
1122
|
type: "text",
|
|
739
1123
|
question: {
|
|
740
|
-
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",
|
|
741
|
-
en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings."
|
|
1124
|
+
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",
|
|
1125
|
+
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."
|
|
742
1126
|
},
|
|
743
1127
|
async fetchOptions(state) {
|
|
744
1128
|
if (state.properties?.length) return [];
|
|
@@ -752,85 +1136,107 @@ var googleAnalyticsSetupFlow = {
|
|
|
752
1136
|
],
|
|
753
1137
|
async finalize(state, rt) {
|
|
754
1138
|
const sections = ["## Google Analytics", ""];
|
|
1139
|
+
let serviceAccountEmail = null;
|
|
1140
|
+
try {
|
|
1141
|
+
const keyJsonBase64 = rt.params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
1142
|
+
if (keyJsonBase64) {
|
|
1143
|
+
serviceAccountEmail = decodeServiceAccount(keyJsonBase64).client_email ?? null;
|
|
1144
|
+
}
|
|
1145
|
+
} catch {
|
|
1146
|
+
}
|
|
1147
|
+
let targetPropertyIds = [];
|
|
1148
|
+
let accountLabel = null;
|
|
1149
|
+
let accountSummary;
|
|
755
1150
|
if (state.account && state.properties) {
|
|
756
|
-
let summaries = [];
|
|
757
1151
|
try {
|
|
758
|
-
summaries = await listAccountSummaries(rt.params);
|
|
1152
|
+
const summaries = await listAccountSummaries(rt.params);
|
|
1153
|
+
accountSummary = summaries.find(
|
|
1154
|
+
(s) => (s.account ?? s.name) === state.account
|
|
1155
|
+
);
|
|
1156
|
+
accountLabel = accountSummary?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
759
1157
|
} catch {
|
|
1158
|
+
accountLabel = state.account.replace(/^accounts\//, "");
|
|
760
1159
|
}
|
|
761
|
-
|
|
762
|
-
(s) => (s.account ?? s.name) === state.account
|
|
763
|
-
);
|
|
764
|
-
const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
765
|
-
const targetPropertyIds = await resolveSetupSelection({
|
|
1160
|
+
targetPropertyIds = await resolveSetupSelection({
|
|
766
1161
|
selected: state.properties,
|
|
767
1162
|
allSentinel: ALL_PROPERTIES,
|
|
768
|
-
fetchAll: async () => (
|
|
1163
|
+
fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
769
1164
|
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
770
1165
|
});
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
sections.push(
|
|
777
|
-
"| Property ID | Display Name | Time Zone | Currency | Industry |"
|
|
778
|
-
);
|
|
1166
|
+
} else if (state.manualPropertyId) {
|
|
1167
|
+
targetPropertyIds = [state.manualPropertyId];
|
|
1168
|
+
}
|
|
1169
|
+
if (targetPropertyIds.length === 0) {
|
|
779
1170
|
sections.push(
|
|
780
|
-
"
|
|
1171
|
+
"_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._",
|
|
1172
|
+
""
|
|
781
1173
|
);
|
|
782
|
-
|
|
783
|
-
const prop = await getProperty(rt.params, pid).catch(() => null);
|
|
784
|
-
const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
|
|
785
|
-
const timeZone = prop?.timeZone ?? "-";
|
|
786
|
-
const currency = prop?.currencyCode ?? "-";
|
|
787
|
-
const industry = (prop?.industryCategory ?? "-").replace(
|
|
788
|
-
/\|/g,
|
|
789
|
-
"\\|"
|
|
790
|
-
);
|
|
1174
|
+
if (serviceAccountEmail) {
|
|
791
1175
|
sections.push(
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
sections.push("");
|
|
796
|
-
const firstPropertyId = targetPropertyIds[0];
|
|
797
|
-
if (firstPropertyId) {
|
|
798
|
-
const { dimensions, metrics } = await getMetadata(
|
|
799
|
-
rt.params,
|
|
800
|
-
firstPropertyId
|
|
1176
|
+
`_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
|
|
1177
|
+
""
|
|
801
1178
|
);
|
|
802
|
-
appendMetadataSection(sections, dimensions, metrics);
|
|
803
1179
|
}
|
|
804
1180
|
return sections.join("\n");
|
|
805
1181
|
}
|
|
806
|
-
const propertyId = state.manualPropertyId;
|
|
807
|
-
if (propertyId) {
|
|
808
|
-
sections.push(`### Property: ${propertyId}`, "");
|
|
809
|
-
const { dimensions, metrics } = await getMetadata(
|
|
810
|
-
rt.params,
|
|
811
|
-
propertyId
|
|
812
|
-
);
|
|
813
|
-
appendMetadataSection(sections, dimensions, metrics);
|
|
814
|
-
return sections.join("\n");
|
|
815
|
-
}
|
|
816
1182
|
sections.push(
|
|
817
|
-
"
|
|
1183
|
+
"_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._",
|
|
818
1184
|
""
|
|
819
1185
|
);
|
|
1186
|
+
if (accountLabel) sections.push(`### Account: ${accountLabel}`, "");
|
|
1187
|
+
sections.push("### Properties", "");
|
|
1188
|
+
sections.push(
|
|
1189
|
+
"| Property ID | Display Name | Time Zone | Currency | Industry |"
|
|
1190
|
+
);
|
|
1191
|
+
sections.push(
|
|
1192
|
+
"|-------------|--------------|-----------|----------|----------|"
|
|
1193
|
+
);
|
|
1194
|
+
const properties = await Promise.all(
|
|
1195
|
+
targetPropertyIds.map((id) => getProperty(rt.params, id))
|
|
1196
|
+
);
|
|
1197
|
+
for (let i = 0; i < targetPropertyIds.length; i++) {
|
|
1198
|
+
const pid = targetPropertyIds[i];
|
|
1199
|
+
const prop = properties[i];
|
|
1200
|
+
const displayName = escapePipe(prop?.displayName ?? "-");
|
|
1201
|
+
const timeZone = prop?.timeZone ?? "-";
|
|
1202
|
+
const currency = prop?.currencyCode ?? "-";
|
|
1203
|
+
const industry = escapePipe(prop?.industryCategory ?? "-");
|
|
1204
|
+
sections.push(
|
|
1205
|
+
`| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
sections.push("");
|
|
1209
|
+
const propertiesToExplore = targetPropertyIds.slice(
|
|
1210
|
+
0,
|
|
1211
|
+
MAX_PROPERTIES_TO_EXPLORE
|
|
1212
|
+
);
|
|
1213
|
+
for (let i = 0; i < propertiesToExplore.length; i++) {
|
|
1214
|
+
const pid = propertiesToExplore[i];
|
|
1215
|
+
const prop = properties[i];
|
|
1216
|
+
const heading = prop?.displayName && prop.displayName !== "-" ? `### Data exploration: ${prop.displayName} (${pid})` : `### Data exploration: property ${pid}`;
|
|
1217
|
+
sections.push(heading, "");
|
|
1218
|
+
await exploreProperty(sections, rt.params, pid, serviceAccountEmail);
|
|
1219
|
+
}
|
|
1220
|
+
if (targetPropertyIds.length > propertiesToExplore.length) {
|
|
1221
|
+
const skipped = targetPropertyIds.length - propertiesToExplore.length;
|
|
1222
|
+
sections.push(
|
|
1223
|
+
`_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\`._`,
|
|
1224
|
+
""
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
820
1227
|
return sections.join("\n");
|
|
821
1228
|
}
|
|
822
1229
|
};
|
|
823
1230
|
|
|
824
1231
|
// ../connectors/src/connectors/google-analytics/tools/request.ts
|
|
825
1232
|
import { z } from "zod";
|
|
826
|
-
var BASE_URL2 = "https://analyticsdata.googleapis.com
|
|
1233
|
+
var BASE_URL2 = "https://analyticsdata.googleapis.com";
|
|
827
1234
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
828
1235
|
var inputSchema = z.object({
|
|
829
1236
|
toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
|
|
830
1237
|
connectionId: z.string().describe("ID of the Google Analytics connection to use"),
|
|
831
|
-
propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
|
|
832
1238
|
method: z.enum(["GET", "POST"]).describe("HTTP method"),
|
|
833
|
-
path: z.string().describe("
|
|
1239
|
+
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'."),
|
|
834
1240
|
body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
|
|
835
1241
|
});
|
|
836
1242
|
var outputSchema = z.discriminatedUnion("success", [
|
|
@@ -847,11 +1253,10 @@ var outputSchema = z.discriminatedUnion("success", [
|
|
|
847
1253
|
var requestTool = new ConnectorTool({
|
|
848
1254
|
name: "request",
|
|
849
1255
|
description: `Send authenticated requests to the Google Analytics Data API.
|
|
850
|
-
Authentication is handled automatically using a service account
|
|
851
|
-
{propertyId} in the path is automatically replaced with the propertyId parameter.`,
|
|
1256
|
+
Authentication is handled automatically using a service account.`,
|
|
852
1257
|
inputSchema,
|
|
853
1258
|
outputSchema,
|
|
854
|
-
async execute({ connectionId,
|
|
1259
|
+
async execute({ connectionId, method, path: path2, body }, connections) {
|
|
855
1260
|
const connection2 = connections.find((c) => c.id === connectionId);
|
|
856
1261
|
if (!connection2) {
|
|
857
1262
|
return { success: false, error: `Connection ${connectionId} not found` };
|
|
@@ -871,8 +1276,7 @@ Authentication is handled automatically using a service account.
|
|
|
871
1276
|
if (!token) {
|
|
872
1277
|
return { success: false, error: "Failed to obtain access token" };
|
|
873
1278
|
}
|
|
874
|
-
const
|
|
875
|
-
const url = `${BASE_URL2}${resolvedPath}`;
|
|
1279
|
+
const url = `${BASE_URL2}${path2}`;
|
|
876
1280
|
const controller = new AbortController();
|
|
877
1281
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
878
1282
|
try {
|
|
@@ -919,7 +1323,7 @@ var googleAnalyticsConnector = new ConnectorPlugin({
|
|
|
919
1323
|
systemPrompt: {
|
|
920
1324
|
en: `### Tools
|
|
921
1325
|
|
|
922
|
-
- \`
|
|
1326
|
+
- \`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.
|
|
923
1327
|
|
|
924
1328
|
### Business Logic
|
|
925
1329
|
|
|
@@ -968,8 +1372,8 @@ export default async function handler(c: Context) {
|
|
|
968
1372
|
|
|
969
1373
|
Common operations:
|
|
970
1374
|
|
|
971
|
-
- **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
|
|
972
|
-
- **Run report** (query analytics data): \`POST properties/{propertyId}:runReport\` with a body like:
|
|
1375
|
+
- **Get metadata** (list available dimensions/metrics for a property): \`GET /v1beta/properties/{propertyId}/metadata\`
|
|
1376
|
+
- **Run report** (query analytics data): \`POST /v1beta/properties/{propertyId}:runReport\` with a body like:
|
|
973
1377
|
\`\`\`json
|
|
974
1378
|
{
|
|
975
1379
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -978,7 +1382,7 @@ Common operations:
|
|
|
978
1382
|
"limit": 100
|
|
979
1383
|
}
|
|
980
1384
|
\`\`\`
|
|
981
|
-
- **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
|
|
1385
|
+
- **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
|
|
982
1386
|
|
|
983
1387
|
#### Common dimensions
|
|
984
1388
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -991,7 +1395,7 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
991
1395
|
- Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
|
|
992
1396
|
ja: `### \u30C4\u30FC\u30EB
|
|
993
1397
|
|
|
994
|
-
- \`
|
|
1398
|
+
- \`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
|
|
995
1399
|
|
|
996
1400
|
### Business Logic
|
|
997
1401
|
|
|
@@ -1040,8 +1444,8 @@ export default async function handler(c: Context) {
|
|
|
1040
1444
|
|
|
1041
1445
|
\u4E3B\u306A\u64CD\u4F5C:
|
|
1042
1446
|
|
|
1043
|
-
- **\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\`
|
|
1044
|
-
- **\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:
|
|
1447
|
+
- **\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\`
|
|
1448
|
+
- **\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:
|
|
1045
1449
|
\`\`\`json
|
|
1046
1450
|
{
|
|
1047
1451
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -1050,7 +1454,7 @@ export default async function handler(c: Context) {
|
|
|
1050
1454
|
"limit": 100
|
|
1051
1455
|
}
|
|
1052
1456
|
\`\`\`
|
|
1053
|
-
- **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST properties/{propertyId}:runRealtimeReport\`
|
|
1457
|
+
- **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
|
|
1054
1458
|
|
|
1055
1459
|
#### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
|
|
1056
1460
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -1072,8 +1476,22 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
1072
1476
|
error: "google-analytics: missing service account key"
|
|
1073
1477
|
};
|
|
1074
1478
|
}
|
|
1479
|
+
const propertyId = params[parameters.propertyId.slug];
|
|
1480
|
+
if (!propertyId) {
|
|
1481
|
+
try {
|
|
1482
|
+
const sa = decodeServiceAccount(keyJsonBase64);
|
|
1483
|
+
await getAccessToken(sa);
|
|
1484
|
+
return { success: true };
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1487
|
+
return { success: false, error: msg };
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1075
1490
|
try {
|
|
1076
|
-
const res = await dataFetch(
|
|
1491
|
+
const res = await dataFetch(
|
|
1492
|
+
params,
|
|
1493
|
+
`/properties/${propertyId}/metadata`
|
|
1494
|
+
);
|
|
1077
1495
|
if (!res.ok) {
|
|
1078
1496
|
const body = await res.text().catch(() => res.statusText);
|
|
1079
1497
|
return {
|