@squadbase/vite-server 0.1.17-dev.a9ddcfa → 0.1.17
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 +3281 -731
- package/dist/connectors/airtable-oauth.js +48 -8
- package/dist/connectors/airtable.js +44 -8
- package/dist/connectors/amplitude.js +8 -8
- package/dist/connectors/anthropic.js +2 -2
- package/dist/connectors/asana.js +37 -10
- package/dist/connectors/attio.js +30 -13
- package/dist/connectors/aws-billing.js +8 -8
- package/dist/connectors/azure-sql.js +31 -7
- package/dist/connectors/backlog-api-key.js +40 -15
- package/dist/connectors/clickup.js +50 -10
- package/dist/connectors/cosmosdb.js +12 -12
- package/dist/connectors/customerio.js +8 -8
- package/dist/connectors/dbt.js +686 -25
- package/dist/connectors/freshdesk.js +82 -8
- package/dist/connectors/freshsales.js +8 -8
- package/dist/connectors/freshservice.js +8 -8
- package/dist/connectors/gamma.js +15 -15
- package/dist/connectors/gemini.js +2 -2
- package/dist/connectors/github.js +12 -12
- package/dist/connectors/gmail-oauth.js +10 -10
- package/dist/connectors/gmail.js +4 -4
- package/dist/connectors/google-ads.js +8 -8
- package/dist/connectors/google-analytics-oauth.js +152 -25
- package/dist/connectors/google-analytics.js +490 -96
- package/dist/connectors/google-audit-log.js +4 -4
- package/dist/connectors/google-calendar-oauth.js +61 -15
- package/dist/connectors/google-calendar.js +61 -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 +126 -17
- package/dist/connectors/google-sheets.js +6 -6
- package/dist/connectors/google-slides.js +10 -10
- package/dist/connectors/grafana.js +45 -10
- package/dist/connectors/hubspot-oauth.js +41 -9
- package/dist/connectors/hubspot.js +25 -9
- package/dist/connectors/influxdb.js +8 -8
- package/dist/connectors/intercom-oauth.js +72 -12
- package/dist/connectors/intercom.js +12 -12
- package/dist/connectors/jdbc.js +6 -6
- package/dist/connectors/jira-api-key.js +68 -11
- package/dist/connectors/kintone-api-token.js +66 -18
- package/dist/connectors/kintone.js +54 -11
- package/dist/connectors/linear.js +54 -12
- package/dist/connectors/linkedin-ads.js +41 -14
- package/dist/connectors/mailchimp-oauth.js +6 -6
- package/dist/connectors/mailchimp.js +6 -6
- package/dist/connectors/meta-ads-oauth.js +33 -14
- package/dist/connectors/meta-ads.js +35 -14
- package/dist/connectors/mixpanel.js +8 -8
- package/dist/connectors/monday.js +9 -9
- package/dist/connectors/mongodb.js +8 -8
- package/dist/connectors/notion-oauth.js +60 -11
- package/dist/connectors/notion.js +60 -11
- package/dist/connectors/openai.js +2 -2
- package/dist/connectors/oracle.js +23 -7
- package/dist/connectors/outlook-oauth.js +20 -20
- package/dist/connectors/powerbi-oauth.js +12 -12
- package/dist/connectors/salesforce.js +42 -9
- package/dist/connectors/semrush.js +6 -6
- package/dist/connectors/sentry.js +36 -10
- package/dist/connectors/shopify-oauth.js +43 -10
- package/dist/connectors/shopify.js +8 -8
- package/dist/connectors/sqlserver.js +31 -7
- package/dist/connectors/stripe-api-key.js +66 -15
- package/dist/connectors/stripe-oauth.js +70 -19
- package/dist/connectors/supabase.js +22 -5
- package/dist/connectors/tableau.js +14 -14
- package/dist/connectors/tiktok-ads.js +37 -16
- package/dist/connectors/wix-store.js +8 -8
- package/dist/connectors/zendesk-oauth.js +55 -12
- package/dist/connectors/zendesk.js +12 -12
- package/dist/index.js +3299 -729
- package/dist/main.js +3299 -729
- package/dist/vite-plugin.js +3279 -729
- package/package.json +1 -1
|
@@ -350,7 +350,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
350
350
|
/**
|
|
351
351
|
* Create tools for connections that belong to this connector.
|
|
352
352
|
* Filters connections by connectorKey internally.
|
|
353
|
-
* Returns tools keyed as
|
|
353
|
+
* Returns tools keyed as `connector_${connectorKey}_${toolName}`.
|
|
354
354
|
*/
|
|
355
355
|
createTools(connections, config, opts) {
|
|
356
356
|
const myConnections = connections.filter(
|
|
@@ -360,7 +360,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
360
360
|
for (const t of Object.values(this.tools)) {
|
|
361
361
|
const tool = t.createTool(myConnections, config);
|
|
362
362
|
const originalToModelOutput = tool.toModelOutput;
|
|
363
|
-
result[
|
|
363
|
+
result[`connector_${this.connectorKey}_${t.name}`] = {
|
|
364
364
|
...tool,
|
|
365
365
|
toModelOutput: async (options) => {
|
|
366
366
|
if (!originalToModelOutput) {
|
|
@@ -490,10 +490,10 @@ var AUTH_TYPES = {
|
|
|
490
490
|
// ../connectors/src/connectors/google-analytics/setup.ts
|
|
491
491
|
var googleAnalyticsOnboarding = new ConnectorOnboarding({
|
|
492
492
|
dataOverviewInstructions: {
|
|
493
|
-
en: `1. Call
|
|
494
|
-
2. Call
|
|
495
|
-
ja: `1.
|
|
496
|
-
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`
|
|
497
497
|
}
|
|
498
498
|
});
|
|
499
499
|
|
|
@@ -601,7 +601,17 @@ async function dataFetch(params, path2, init) {
|
|
|
601
601
|
// ../connectors/src/connectors/google-analytics/setup-flow.ts
|
|
602
602
|
var ALL_PROPERTIES = "__ALL_PROPERTIES__";
|
|
603
603
|
var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
|
|
604
|
-
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;
|
|
605
615
|
async function listAccountSummaries(params) {
|
|
606
616
|
const summaries = [];
|
|
607
617
|
let pageToken;
|
|
@@ -624,9 +634,13 @@ function propertyIdFromResource(resource) {
|
|
|
624
634
|
return (resource ?? "").replace(/^properties\//, "");
|
|
625
635
|
}
|
|
626
636
|
async function getProperty(params, propertyId) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
+
}
|
|
630
644
|
}
|
|
631
645
|
async function getMetadata(params, propertyId) {
|
|
632
646
|
try {
|
|
@@ -634,41 +648,387 @@ async function getMetadata(params, propertyId) {
|
|
|
634
648
|
params,
|
|
635
649
|
`/properties/${propertyId}/metadata`
|
|
636
650
|
);
|
|
637
|
-
if (!res.ok) return
|
|
638
|
-
|
|
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) {
|
|
639
676
|
return {
|
|
640
|
-
|
|
641
|
-
|
|
677
|
+
ok: false,
|
|
678
|
+
status: "network",
|
|
679
|
+
body: err instanceof Error ? err.message : String(err)
|
|
642
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] ?? {});
|
|
643
698
|
} catch {
|
|
644
|
-
return
|
|
699
|
+
return requests.map(() => ({}));
|
|
645
700
|
}
|
|
646
701
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
702
|
+
var LAST_30D = { startDate: "30daysAgo", endDate: "today" };
|
|
703
|
+
var PREV_30D = { startDate: "60daysAgo", endDate: "31daysAgo" };
|
|
704
|
+
var HEADLINE_METRICS = [
|
|
705
|
+
"activeUsers",
|
|
706
|
+
"newUsers",
|
|
707
|
+
"sessions",
|
|
708
|
+
"screenPageViews",
|
|
709
|
+
"engagementRate",
|
|
710
|
+
"averageSessionDuration",
|
|
711
|
+
"keyEvents",
|
|
712
|
+
"totalRevenue"
|
|
713
|
+
];
|
|
714
|
+
function buildHeadlineRequest() {
|
|
715
|
+
return {
|
|
716
|
+
dateRanges: [
|
|
717
|
+
{ ...LAST_30D, name: "current" },
|
|
718
|
+
{ ...PREV_30D, name: "previous" }
|
|
719
|
+
],
|
|
720
|
+
metrics: HEADLINE_METRICS.map((name) => ({ name }))
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function buildTopDimensionRequest(dimension, metric, limit) {
|
|
724
|
+
return {
|
|
725
|
+
dateRanges: [LAST_30D],
|
|
726
|
+
dimensions: [{ name: dimension }],
|
|
727
|
+
metrics: [{ name: metric }],
|
|
728
|
+
limit,
|
|
729
|
+
orderBys: [{ desc: true, metric: { metricName: metric } }]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function buildTopPagesRequest() {
|
|
733
|
+
return {
|
|
734
|
+
dateRanges: [LAST_30D],
|
|
735
|
+
dimensions: [{ name: "pagePath" }],
|
|
736
|
+
metrics: [
|
|
737
|
+
{ name: "screenPageViews" },
|
|
738
|
+
{ name: "activeUsers" },
|
|
739
|
+
{ name: "averageSessionDuration" }
|
|
740
|
+
],
|
|
741
|
+
limit: TOP_N_PAGES,
|
|
742
|
+
orderBys: [{ desc: true, metric: { metricName: "screenPageViews" } }]
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function buildTopEventsRequest() {
|
|
746
|
+
return {
|
|
747
|
+
dateRanges: [LAST_30D],
|
|
748
|
+
dimensions: [{ name: "eventName" }],
|
|
749
|
+
metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
|
|
750
|
+
limit: TOP_N_EVENTS,
|
|
751
|
+
orderBys: [{ desc: true, metric: { metricName: "eventCount" } }]
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function escapePipe(s) {
|
|
755
|
+
return s.replace(/\|/g, "\\|");
|
|
756
|
+
}
|
|
757
|
+
function truncate(s, max) {
|
|
758
|
+
if (!s) return "";
|
|
759
|
+
if (s.length <= max) return s;
|
|
760
|
+
return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
761
|
+
}
|
|
762
|
+
function formatNumber(raw) {
|
|
763
|
+
if (raw == null || raw === "") return "-";
|
|
764
|
+
const n = Number(raw);
|
|
765
|
+
if (!Number.isFinite(n)) return raw;
|
|
766
|
+
if (Number.isInteger(n)) return n.toLocaleString("en-US");
|
|
767
|
+
return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
768
|
+
}
|
|
769
|
+
function formatPercent(raw) {
|
|
770
|
+
if (raw == null || raw === "") return "-";
|
|
771
|
+
const n = Number(raw);
|
|
772
|
+
if (!Number.isFinite(n)) return raw;
|
|
773
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
774
|
+
}
|
|
775
|
+
function formatDuration(rawSeconds) {
|
|
776
|
+
if (rawSeconds == null || rawSeconds === "") return "-";
|
|
777
|
+
const s = Math.round(Number(rawSeconds));
|
|
778
|
+
if (!Number.isFinite(s)) return rawSeconds;
|
|
779
|
+
if (s < 60) return `${s}s`;
|
|
780
|
+
const mins = Math.floor(s / 60);
|
|
781
|
+
const secs = s % 60;
|
|
782
|
+
return `${mins}m ${secs}s`;
|
|
783
|
+
}
|
|
784
|
+
function deltaArrow(curr, prev) {
|
|
785
|
+
if (!Number.isFinite(prev) || prev === 0) return "";
|
|
786
|
+
const pct = (curr - prev) / prev * 100;
|
|
787
|
+
if (Math.abs(pct) < 0.5) return " (\u2248 flat)";
|
|
788
|
+
const sign = pct > 0 ? "\u2191" : "\u2193";
|
|
789
|
+
return ` (${sign} ${Math.abs(pct).toFixed(1)}% vs prior 30d)`;
|
|
790
|
+
}
|
|
791
|
+
function pivotComparisonReport(report) {
|
|
792
|
+
const out = /* @__PURE__ */ new Map();
|
|
793
|
+
const headers = report.metricHeaders ?? [];
|
|
794
|
+
const rows = report.rows ?? [];
|
|
795
|
+
for (let i = 0; i < headers.length; i++) {
|
|
796
|
+
const name = headers[i]?.name;
|
|
797
|
+
if (!name) continue;
|
|
798
|
+
out.set(name, {
|
|
799
|
+
current: Number(rows[0]?.metricValues?.[i]?.value ?? "0"),
|
|
800
|
+
previous: Number(rows[1]?.metricValues?.[i]?.value ?? "0")
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
return out;
|
|
804
|
+
}
|
|
805
|
+
function renderHeadline(sections, report) {
|
|
806
|
+
const m = pivotComparisonReport(report);
|
|
807
|
+
if (m.size === 0) return;
|
|
808
|
+
sections.push("#### Headline metrics (last 30 days)", "");
|
|
809
|
+
const lines = [];
|
|
810
|
+
const push = (label, key, formatter) => {
|
|
811
|
+
const cell = m.get(key);
|
|
812
|
+
if (!cell) return;
|
|
813
|
+
const formatted = formatter(String(cell.current));
|
|
814
|
+
const delta = deltaArrow(cell.current, cell.previous);
|
|
815
|
+
lines.push([label, `${formatted}${delta}`]);
|
|
816
|
+
};
|
|
817
|
+
push("Active users", "activeUsers", formatNumber);
|
|
818
|
+
push("New users", "newUsers", formatNumber);
|
|
819
|
+
push("Sessions", "sessions", formatNumber);
|
|
820
|
+
push("Page / screen views", "screenPageViews", formatNumber);
|
|
821
|
+
push("Engagement rate", "engagementRate", formatPercent);
|
|
822
|
+
push("Avg session duration", "averageSessionDuration", formatDuration);
|
|
823
|
+
push("Key events (conversions)", "keyEvents", formatNumber);
|
|
824
|
+
const rev = m.get("totalRevenue");
|
|
825
|
+
if (rev && rev.current > 0) {
|
|
826
|
+
lines.push([
|
|
827
|
+
"Total revenue",
|
|
828
|
+
`${formatNumber(String(rev.current))}${deltaArrow(rev.current, rev.previous)}`
|
|
829
|
+
]);
|
|
830
|
+
}
|
|
831
|
+
for (const [k, v] of lines) sections.push(`- **${k}**: ${v}`);
|
|
832
|
+
sections.push("");
|
|
833
|
+
}
|
|
834
|
+
function renderBreakdown(sections, title, blurb, report, topN, metricFormatter = formatNumber) {
|
|
835
|
+
const rows = report.rows ?? [];
|
|
836
|
+
if (rows.length === 0) return;
|
|
837
|
+
const dim = report.dimensionHeaders?.[0]?.name ?? "dimension";
|
|
838
|
+
const metricName = report.metricHeaders?.[0]?.name ?? "metric";
|
|
839
|
+
sections.push(`#### ${title}`, "", blurb, "");
|
|
840
|
+
for (const row of rows.slice(0, topN)) {
|
|
841
|
+
const label = row.dimensionValues?.[0]?.value || `(empty ${dim})`;
|
|
842
|
+
const value = metricFormatter(row.metricValues?.[0]?.value);
|
|
843
|
+
sections.push(`- ${label}: ${value} ${metricName}`);
|
|
844
|
+
}
|
|
845
|
+
sections.push("");
|
|
846
|
+
}
|
|
847
|
+
function renderTopPages(sections, report) {
|
|
848
|
+
const rows = report.rows ?? [];
|
|
849
|
+
if (rows.length === 0) return;
|
|
850
|
+
sections.push(
|
|
851
|
+
`#### Top pages by views (last 30 days)`,
|
|
852
|
+
"",
|
|
853
|
+
"_The most-visited pages \u2014 strong candidates for content-performance dashboards._",
|
|
854
|
+
""
|
|
855
|
+
);
|
|
856
|
+
sections.push("| Path | Page views | Active users | Avg session duration |");
|
|
857
|
+
sections.push("|------|------------|--------------|----------------------|");
|
|
858
|
+
for (const row of rows.slice(0, TOP_N_PAGES)) {
|
|
859
|
+
const path2 = truncate(row.dimensionValues?.[0]?.value, PAGE_PATH_MAX) || "-";
|
|
860
|
+
sections.push(
|
|
861
|
+
`| ${escapePipe(path2)} | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
|
|
862
|
+
row.metricValues?.[1]?.value
|
|
863
|
+
)} | ${formatDuration(row.metricValues?.[2]?.value)} |`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
sections.push("");
|
|
867
|
+
}
|
|
868
|
+
function renderTopEvents(sections, report) {
|
|
869
|
+
const rows = report.rows ?? [];
|
|
870
|
+
if (rows.length === 0) return;
|
|
871
|
+
sections.push(
|
|
872
|
+
`#### Top events (last 30 days)`,
|
|
873
|
+
"",
|
|
874
|
+
"_Standard auto-collected events (`page_view`, `session_start`, \u2026) plus any custom events the team has set up. The custom ones are usually the most interesting for funnel / conversion dashboards._",
|
|
875
|
+
""
|
|
876
|
+
);
|
|
877
|
+
sections.push("| Event | Event count | Unique users |");
|
|
878
|
+
sections.push("|-------|-------------|--------------|");
|
|
879
|
+
for (const row of rows.slice(0, TOP_N_EVENTS)) {
|
|
880
|
+
const name = truncate(row.dimensionValues?.[0]?.value, EVENT_NAME_MAX) || "-";
|
|
881
|
+
sections.push(
|
|
882
|
+
`| \`${escapePipe(name)}\` | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
|
|
883
|
+
row.metricValues?.[1]?.value
|
|
884
|
+
)} |`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
sections.push("");
|
|
888
|
+
}
|
|
889
|
+
function renderMetadata(sections, metadata) {
|
|
890
|
+
const dims = metadata.dimensions ?? [];
|
|
891
|
+
const mets = metadata.metrics ?? [];
|
|
892
|
+
if (dims.length === 0 && mets.length === 0) return;
|
|
893
|
+
const customDims = dims.filter((d) => d.customDefinition);
|
|
894
|
+
const customMets = mets.filter((m) => m.customDefinition);
|
|
895
|
+
const standardDims = dims.filter((d) => !d.customDefinition);
|
|
896
|
+
const standardMets = mets.filter((m) => !m.customDefinition);
|
|
897
|
+
sections.push("#### Available query surface", "");
|
|
898
|
+
sections.push(
|
|
899
|
+
"_Use these in custom `runReport` calls. Custom definitions reveal what the team specifically tracks beyond GA4's defaults._",
|
|
900
|
+
""
|
|
901
|
+
);
|
|
902
|
+
if (customDims.length > 0 || customMets.length > 0) {
|
|
903
|
+
sections.push(`##### Custom definitions`, "");
|
|
904
|
+
for (const d of customDims.slice(0, CUSTOM_METADATA_LIMIT)) {
|
|
905
|
+
const desc = d.description ? ` \u2014 ${truncate(d.description, DESCRIPTION_MAX)}` : "";
|
|
906
|
+
sections.push(`- dimension \`${d.apiName ?? d.uiName}\`${desc}`);
|
|
652
907
|
}
|
|
653
|
-
if (
|
|
908
|
+
if (customDims.length > CUSTOM_METADATA_LIMIT) {
|
|
654
909
|
sections.push(
|
|
655
|
-
`- \u2026and ${
|
|
910
|
+
`- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
|
|
656
911
|
);
|
|
657
912
|
}
|
|
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)"}`);
|
|
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}`);
|
|
664
916
|
}
|
|
665
|
-
if (
|
|
917
|
+
if (customMets.length > CUSTOM_METADATA_LIMIT) {
|
|
666
918
|
sections.push(
|
|
667
|
-
`- \u2026and ${
|
|
919
|
+
`- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
|
|
668
920
|
);
|
|
669
921
|
}
|
|
670
922
|
sections.push("");
|
|
671
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);
|
|
672
1032
|
}
|
|
673
1033
|
var googleAnalyticsSetupFlow = {
|
|
674
1034
|
initialState: () => ({}),
|
|
@@ -737,8 +1097,8 @@ var googleAnalyticsSetupFlow = {
|
|
|
737
1097
|
slug: "manualPropertyId",
|
|
738
1098
|
type: "text",
|
|
739
1099
|
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."
|
|
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."
|
|
742
1102
|
},
|
|
743
1103
|
async fetchOptions(state) {
|
|
744
1104
|
if (state.properties?.length) return [];
|
|
@@ -752,85 +1112,107 @@ var googleAnalyticsSetupFlow = {
|
|
|
752
1112
|
],
|
|
753
1113
|
async finalize(state, rt) {
|
|
754
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;
|
|
755
1126
|
if (state.account && state.properties) {
|
|
756
|
-
let summaries = [];
|
|
757
1127
|
try {
|
|
758
|
-
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\//, "");
|
|
759
1133
|
} catch {
|
|
1134
|
+
accountLabel = state.account.replace(/^accounts\//, "");
|
|
760
1135
|
}
|
|
761
|
-
|
|
762
|
-
(s) => (s.account ?? s.name) === state.account
|
|
763
|
-
);
|
|
764
|
-
const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
|
|
765
|
-
const targetPropertyIds = await resolveSetupSelection({
|
|
1136
|
+
targetPropertyIds = await resolveSetupSelection({
|
|
766
1137
|
selected: state.properties,
|
|
767
1138
|
allSentinel: ALL_PROPERTIES,
|
|
768
|
-
fetchAll: async () => (
|
|
1139
|
+
fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
|
|
769
1140
|
limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
|
|
770
1141
|
});
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
sections.push(
|
|
777
|
-
"| Property ID | Display Name | Time Zone | Currency | Industry |"
|
|
778
|
-
);
|
|
1142
|
+
} else if (state.manualPropertyId) {
|
|
1143
|
+
targetPropertyIds = [state.manualPropertyId];
|
|
1144
|
+
}
|
|
1145
|
+
if (targetPropertyIds.length === 0) {
|
|
779
1146
|
sections.push(
|
|
780
|
-
"
|
|
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
|
+
""
|
|
781
1149
|
);
|
|
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
|
-
);
|
|
1150
|
+
if (serviceAccountEmail) {
|
|
791
1151
|
sections.push(
|
|
792
|
-
|
|
1152
|
+
`_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
|
|
1153
|
+
""
|
|
793
1154
|
);
|
|
794
1155
|
}
|
|
795
|
-
sections.push("");
|
|
796
|
-
const firstPropertyId = targetPropertyIds[0];
|
|
797
|
-
if (firstPropertyId) {
|
|
798
|
-
const { dimensions, metrics } = await getMetadata(
|
|
799
|
-
rt.params,
|
|
800
|
-
firstPropertyId
|
|
801
|
-
);
|
|
802
|
-
appendMetadataSection(sections, dimensions, metrics);
|
|
803
|
-
}
|
|
804
|
-
return sections.join("\n");
|
|
805
|
-
}
|
|
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
1156
|
return sections.join("\n");
|
|
815
1157
|
}
|
|
816
1158
|
sections.push(
|
|
817
|
-
"
|
|
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._",
|
|
818
1160
|
""
|
|
819
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
|
+
}
|
|
820
1203
|
return sections.join("\n");
|
|
821
1204
|
}
|
|
822
1205
|
};
|
|
823
1206
|
|
|
824
1207
|
// ../connectors/src/connectors/google-analytics/tools/request.ts
|
|
825
1208
|
import { z } from "zod";
|
|
826
|
-
var BASE_URL2 = "https://analyticsdata.googleapis.com
|
|
1209
|
+
var BASE_URL2 = "https://analyticsdata.googleapis.com";
|
|
827
1210
|
var REQUEST_TIMEOUT_MS = 6e4;
|
|
828
1211
|
var inputSchema = z.object({
|
|
829
1212
|
toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
|
|
830
1213
|
connectionId: z.string().describe("ID of the Google Analytics connection to use"),
|
|
831
|
-
propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
|
|
832
1214
|
method: z.enum(["GET", "POST"]).describe("HTTP method"),
|
|
833
|
-
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'."),
|
|
834
1216
|
body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
|
|
835
1217
|
});
|
|
836
1218
|
var outputSchema = z.discriminatedUnion("success", [
|
|
@@ -847,11 +1229,10 @@ var outputSchema = z.discriminatedUnion("success", [
|
|
|
847
1229
|
var requestTool = new ConnectorTool({
|
|
848
1230
|
name: "request",
|
|
849
1231
|
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.`,
|
|
1232
|
+
Authentication is handled automatically using a service account.`,
|
|
852
1233
|
inputSchema,
|
|
853
1234
|
outputSchema,
|
|
854
|
-
async execute({ connectionId,
|
|
1235
|
+
async execute({ connectionId, method, path: path2, body }, connections) {
|
|
855
1236
|
const connection2 = connections.find((c) => c.id === connectionId);
|
|
856
1237
|
if (!connection2) {
|
|
857
1238
|
return { success: false, error: `Connection ${connectionId} not found` };
|
|
@@ -871,8 +1252,7 @@ Authentication is handled automatically using a service account.
|
|
|
871
1252
|
if (!token) {
|
|
872
1253
|
return { success: false, error: "Failed to obtain access token" };
|
|
873
1254
|
}
|
|
874
|
-
const
|
|
875
|
-
const url = `${BASE_URL2}${resolvedPath}`;
|
|
1255
|
+
const url = `${BASE_URL2}${path2}`;
|
|
876
1256
|
const controller = new AbortController();
|
|
877
1257
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
878
1258
|
try {
|
|
@@ -919,7 +1299,7 @@ var googleAnalyticsConnector = new ConnectorPlugin({
|
|
|
919
1299
|
systemPrompt: {
|
|
920
1300
|
en: `### Tools
|
|
921
1301
|
|
|
922
|
-
- \`
|
|
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.
|
|
923
1303
|
|
|
924
1304
|
### Business Logic
|
|
925
1305
|
|
|
@@ -968,8 +1348,8 @@ export default async function handler(c: Context) {
|
|
|
968
1348
|
|
|
969
1349
|
Common operations:
|
|
970
1350
|
|
|
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:
|
|
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:
|
|
973
1353
|
\`\`\`json
|
|
974
1354
|
{
|
|
975
1355
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -978,7 +1358,7 @@ Common operations:
|
|
|
978
1358
|
"limit": 100
|
|
979
1359
|
}
|
|
980
1360
|
\`\`\`
|
|
981
|
-
- **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
|
|
1361
|
+
- **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
|
|
982
1362
|
|
|
983
1363
|
#### Common dimensions
|
|
984
1364
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -991,7 +1371,7 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
991
1371
|
- Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
|
|
992
1372
|
ja: `### \u30C4\u30FC\u30EB
|
|
993
1373
|
|
|
994
|
-
- \`
|
|
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
|
|
995
1375
|
|
|
996
1376
|
### Business Logic
|
|
997
1377
|
|
|
@@ -1040,8 +1420,8 @@ export default async function handler(c: Context) {
|
|
|
1040
1420
|
|
|
1041
1421
|
\u4E3B\u306A\u64CD\u4F5C:
|
|
1042
1422
|
|
|
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:
|
|
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:
|
|
1045
1425
|
\`\`\`json
|
|
1046
1426
|
{
|
|
1047
1427
|
"dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
|
|
@@ -1050,7 +1430,7 @@ export default async function handler(c: Context) {
|
|
|
1050
1430
|
"limit": 100
|
|
1051
1431
|
}
|
|
1052
1432
|
\`\`\`
|
|
1053
|
-
- **\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\`
|
|
1054
1434
|
|
|
1055
1435
|
#### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
|
|
1056
1436
|
date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
|
|
@@ -1072,8 +1452,22 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
|
|
|
1072
1452
|
error: "google-analytics: missing service account key"
|
|
1073
1453
|
};
|
|
1074
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
|
+
}
|
|
1075
1466
|
try {
|
|
1076
|
-
const res = await dataFetch(
|
|
1467
|
+
const res = await dataFetch(
|
|
1468
|
+
params,
|
|
1469
|
+
`/properties/${propertyId}/metadata`
|
|
1470
|
+
);
|
|
1077
1471
|
if (!res.ok) {
|
|
1078
1472
|
const body = await res.text().catch(() => res.statusText);
|
|
1079
1473
|
return {
|