Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 12 additions & 75 deletions google-analytics/server/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import { z } from "zod";

// ── Shared primitives ────────────────────────────────────────────────────────

/** ISO-8601 timestamp string (e.g. "2024-01-15T10:30:00Z"). */
const Timestamp = z.string();

/** Represents a date used in annotation. */
const DateSchema = z.object({
year: z.number().int().optional(),
month: z.number().int().optional(),
Expand All @@ -23,105 +21,71 @@ const DateSchema = z.object({
// ── Admin API — Account Summaries ────────────────────────────────────────────

export const PropertySummarySchema = z.object({
/** Resource name, e.g. "properties/123456". */
property: z.string(),
displayName: z.string(),
/** e.g. "PROPERTY_TYPE_ORDINARY" | "PROPERTY_TYPE_ROLLUP" | "PROPERTY_TYPE_SUBPROPERTY" */
propertyType: z.string().optional(),
/** Parent resource name, e.g. "accounts/123456". */
parent: z.string().optional(),
});

export const AccountSummarySchema = z.object({
/** Resource name, e.g. "accountSummaries/123456". */
name: z.string(),
/** Account resource name, e.g. "accounts/123456". */
account: z.string().optional(),
displayName: z.string().optional(),
propertySummaries: z.array(PropertySummarySchema).optional(),
});

export const AccountSummariesResponseSchema = z.object({
response: z.object({
accountSummaries: z.array(AccountSummarySchema).optional(),
}),
export const AccountSummariesOutputSchema = z.object({
accountSummaries: z.array(AccountSummarySchema).optional(),
});

// ── Admin API — Property ─────────────────────────────────────────────────────

export const PropertySchema = z.object({
/** Resource name, e.g. "properties/123456". */
name: z.string().optional(),
/** Parent resource, e.g. "accounts/123456" or "properties/123" for sub-properties. */
parent: z.string().optional(),
createTime: Timestamp.optional(),
updateTime: Timestamp.optional(),
displayName: z.string().optional(),
industryCategory: z.string().optional(),
timeZone: z.string().optional(),
currencyCode: z.string().optional(),
/** e.g. "GOOGLE_ANALYTICS_STANDARD" | "GOOGLE_ANALYTICS_360" */
serviceLevel: z.string().optional(),
/** e.g. "PROPERTY_TYPE_ORDINARY" | "PROPERTY_TYPE_ROLLUP" | "PROPERTY_TYPE_SUBPROPERTY" */
propertyType: z.string().optional(),
account: z.string().optional(),
deleteTime: Timestamp.optional(),
expireTime: Timestamp.optional(),
});

export const PropertyResponseSchema = z.object({
response: PropertySchema,
});

// ── Admin API — Custom Dimensions & Metrics ──────────────────────────────────

export const CustomDimensionSchema = z.object({
/** Resource name, e.g. "properties/123/customDimensions/456". */
name: z.string().optional(),
/** The parameter name used in events / user properties. */
parameterName: z.string(),
displayName: z.string().optional(),
description: z.string().optional(),
/** "EVENT" | "USER" | "ITEM" */
scope: z.string().optional(),
disallowAdsPersonalization: z.boolean().optional(),
});

export const CustomMetricSchema = z.object({
/** Resource name. */
name: z.string().optional(),
parameterName: z.string(),
displayName: z.string().optional(),
description: z.string().optional(),
/** e.g. "STANDARD" | "CURRENCY" | "FEET" | "METERS" | "KILOMETERS" | "MILES" | "MILLISECONDS" | "SECONDS" | "MINUTES" | "HOURS" */
measurementUnit: z.string().optional(),
/** "EVENT" | "ITEM" */
scope: z.string().optional(),
/** e.g. ["COST_DATA", "REVENUE_DATA"] */
restrictedMetricType: z.array(z.string()).optional(),
});

export const CustomDimensionsListSchema = z.object({
export const CustomDimensionsAndMetricsOutputSchema = z.object({
customDimensions: z.array(CustomDimensionSchema).optional(),
});

export const CustomMetricsListSchema = z.object({
customMetrics: z.array(CustomMetricSchema).optional(),
});

export const CustomDimensionsAndMetricsResponseSchema = z.object({
/** Result of listCustomDimensions — `{ customDimensions: [...] }` */
dimensions: CustomDimensionsListSchema,
/** Result of listCustomMetrics — `{ customMetrics: [...] }` */
metrics: CustomMetricsListSchema,
});

// ── Admin API — Google Ads Links ─────────────────────────────────────────────

export const GoogleAdsLinkSchema = z.object({
/** Resource name. */
name: z.string().optional(),
/** Google Ads Customer ID (without hyphens). */
customerId: z.string().optional(),
canManageClients: z.boolean().optional(),
adsPersonalizationEnabled: z.boolean().optional(),
Expand All @@ -130,29 +94,23 @@ export const GoogleAdsLinkSchema = z.object({
updateTime: Timestamp.optional(),
});

export const GoogleAdsLinksResponseSchema = z.object({
response: z.object({
googleAdsLinks: z.array(GoogleAdsLinkSchema).optional(),
}),
export const GoogleAdsLinksOutputSchema = z.object({
googleAdsLinks: z.array(GoogleAdsLinkSchema).optional(),
});

// ── Admin API — Property Annotations ─────────────────────────────────────────

export const ReportingDataAnnotationSchema = z.object({
/** Resource name. */
name: z.string().optional(),
annotationDate: DateSchema.optional(),
title: z.string().optional(),
description: z.string().optional(),
systemGenerated: z.boolean().optional(),
/** "RED" | "ORANGE" | "YELLOW" | "GREEN" | "BLUE" | "PURPLE" | "PINK" */
color: z.string().optional(),
});

export const PropertyAnnotationsResponseSchema = z.object({
response: z.object({
reportingDataAnnotations: z.array(ReportingDataAnnotationSchema).optional(),
}),
export const PropertyAnnotationsOutputSchema = z.object({
reportingDataAnnotations: z.array(ReportingDataAnnotationSchema).optional(),
});

// ── Data API — shared building blocks ────────────────────────────────────────
Expand All @@ -163,7 +121,6 @@ export const DimensionHeaderSchema = z.object({

export const MetricHeaderSchema = z.object({
name: z.string(),
/** e.g. "TYPE_INTEGER" | "TYPE_FLOAT" | "TYPE_SECONDS" | "TYPE_MILLISECONDS" | "TYPE_MINUTES" | "TYPE_HOURS" | "TYPE_STANDARD" | "TYPE_CURRENCY" | "TYPE_FEET" | "TYPE_MILES" | "TYPE_METERS" | "TYPE_KILOMETERS" */
type: z.string().optional(),
});

Expand All @@ -188,7 +145,6 @@ export const ResponseMetaDataSchema = z.object({
samplingMetadatas: z.array(z.unknown()).optional(),
});

/** Quota state returned when `returnPropertyQuota: true`. */
export const PropertyQuotaSchema = z
.object({
tokensPerDay: z
Expand All @@ -214,7 +170,7 @@ export const PropertyQuotaSchema = z

// ── Data API — runReport ──────────────────────────────────────────────────────

export const RunReportResponseSchema = z.object({
export const RunReportOutputSchema = z.object({
dimensionHeaders: z.array(DimensionHeaderSchema).optional(),
metricHeaders: z.array(MetricHeaderSchema).optional(),
rows: z.array(RowSchema).optional(),
Expand All @@ -224,17 +180,12 @@ export const RunReportResponseSchema = z.object({
rowCount: z.number().optional(),
metadata: ResponseMetaDataSchema.optional(),
propertyQuota: PropertyQuotaSchema.optional(),
/** Always "analyticsData#runReport". */
kind: z.string().optional(),
});

export const RunReportOutputSchema = z.object({
response: RunReportResponseSchema,
});

// ── Data API — runRealtimeReport ─────────────────────────────────────────────

export const RunRealtimeReportResponseSchema = z.object({
export const RunRealtimeReportOutputSchema = z.object({
dimensionHeaders: z.array(DimensionHeaderSchema).optional(),
metricHeaders: z.array(MetricHeaderSchema).optional(),
rows: z.array(RowSchema).optional(),
Expand All @@ -243,26 +194,17 @@ export const RunRealtimeReportResponseSchema = z.object({
minimums: z.array(RowSchema).optional(),
rowCount: z.number().optional(),
propertyQuota: PropertyQuotaSchema.optional(),
/** Always "analyticsData#runRealtimeReport". */
kind: z.string().optional(),
});

export const RunRealtimeReportOutputSchema = z.object({
response: RunRealtimeReportResponseSchema,
});

// ── Data API — runFunnelReport ────────────────────────────────────────────────

export const FunnelResponseMetaDataSchema = z.object({
const FunnelResponseMetaDataSchema = z.object({
samplingMetadatas: z.array(z.unknown()).optional(),
schemaRestrictionResponse: z.unknown().optional(),
});

/**
* Funnel response rows share the same Row structure (dimensionValues +
* metricValues), grouped in funnelTable and funnelVisualization sub-objects.
*/
export const FunnelSubReportSchema = z.object({
const FunnelSubReportSchema = z.object({
dimensionHeaders: z.array(DimensionHeaderSchema).optional(),
metricHeaders: z.array(MetricHeaderSchema).optional(),
rows: z.array(RowSchema).optional(),
Expand All @@ -273,14 +215,9 @@ export const FunnelSubReportSchema = z.object({
metadata: FunnelResponseMetaDataSchema.optional(),
});

export const RunFunnelReportResponseSchema = z.object({
export const RunFunnelReportOutputSchema = z.object({
funnelTable: FunnelSubReportSchema.optional(),
funnelVisualization: FunnelSubReportSchema.optional(),
propertyQuota: PropertyQuotaSchema.optional(),
/** Always "analyticsData#runFunnelReport". */
kind: z.string().optional(),
});

export const RunFunnelReportOutputSchema = z.object({
response: RunFunnelReportResponseSchema,
});
7 changes: 3 additions & 4 deletions google-analytics/server/tools/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import { z } from "zod";
import { createPrivateTool } from "@decocms/runtime/tools";
import type { Env } from "../../shared/deco.gen.ts";
import { GaClient } from "../lib/ga-client.ts";
import { AccountSummariesResponseSchema } from "../lib/schemas.ts";
import { AccountSummariesOutputSchema } from "../lib/schemas.ts";

export const getAccountSummariesTool = (env: Env) =>
createPrivateTool({
id: "get-account-summaries",
description:
"Retrieves information about the user's Google Analytics accounts and properties.",
inputSchema: z.object({}),
outputSchema: AccountSummariesResponseSchema,
outputSchema: AccountSummariesOutputSchema,
execute: async () => {
const client = GaClient.fromEnv(env);

try {
const result = await client.listAccountSummaries();
return AccountSummariesResponseSchema.parse({ response: result });
return AccountSummariesOutputSchema.parse(result);
} catch (error) {
throw new Error(
`Failed to retrieve account summaries: ${error instanceof Error ? error.message : String(error)}`,
Expand Down
8 changes: 3 additions & 5 deletions google-analytics/server/tools/ads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { z } from "zod";
import { createPrivateTool } from "@decocms/runtime/tools";
import type { Env } from "../../shared/deco.gen.ts";
import { GaClient } from "../lib/ga-client.ts";
import { GoogleAdsLinksResponseSchema } from "../lib/schemas.ts";
import { GoogleAdsLinksOutputSchema } from "../lib/schemas.ts";

const propertySchema = z

.string()

.describe(
"GA4 Property identifier — 'properties/1234567' or just '1234567'.",
);
Expand All @@ -18,12 +16,12 @@ export const listGoogleAdsLinksTool = (env: Env) =>
description:
"Returns a list of links to Google Ads accounts for a GA4 property.",
inputSchema: z.object({ property: propertySchema }),
outputSchema: GoogleAdsLinksResponseSchema,
outputSchema: GoogleAdsLinksOutputSchema,
execute: async ({ context: args }) => {
const client = GaClient.fromEnv(env);
try {
const result = await client.listGoogleAdsLinks(args.property);
return GoogleAdsLinksResponseSchema.parse({ response: result });
return GoogleAdsLinksOutputSchema.parse(result);
} catch (error) {
throw new Error(
`Failed to retrieve Google Ads links: ${error instanceof Error ? error.message : String(error)}`,
Expand Down
8 changes: 3 additions & 5 deletions google-analytics/server/tools/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { z } from "zod";
import { createPrivateTool } from "@decocms/runtime/tools";
import type { Env } from "../../shared/deco.gen.ts";
import { GaClient } from "../lib/ga-client.ts";
import { PropertyAnnotationsResponseSchema } from "../lib/schemas.ts";
import { PropertyAnnotationsOutputSchema } from "../lib/schemas.ts";

const propertySchema = z

.string()

.describe(
"GA4 Property identifier — 'properties/1234567' or just '1234567'.",
);
Expand All @@ -18,12 +16,12 @@ export const listPropertyAnnotationsTool = (env: Env) =>
description:
"Returns timestamped annotations for a GA4 property — useful for correlating traffic changes with events like campaign launches, site releases, or data collection changes.",
inputSchema: z.object({ property: propertySchema }),
outputSchema: PropertyAnnotationsResponseSchema,
outputSchema: PropertyAnnotationsOutputSchema,
execute: async ({ context: args }) => {
const client = GaClient.fromEnv(env);
try {
const result = await client.listPropertyAnnotations(args.property);
return PropertyAnnotationsResponseSchema.parse({ response: result });
return PropertyAnnotationsOutputSchema.parse(result);
} catch (error) {
throw new Error(
`Failed to retrieve property annotations: ${error instanceof Error ? error.message : String(error)}`,
Expand Down
22 changes: 9 additions & 13 deletions google-analytics/server/tools/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { createPrivateTool } from "@decocms/runtime/tools";
import type { Env } from "../../shared/deco.gen.ts";
import { GaClient } from "../lib/ga-client.ts";
import {
PropertyResponseSchema,
CustomDimensionsAndMetricsResponseSchema,
PropertySchema,
CustomDimensionsAndMetricsOutputSchema,
} from "../lib/schemas.ts";

const propertySchema = z

.string()

.describe(
"GA4 Property identifier — 'properties/1234567' or just '1234567'.",
);
Expand All @@ -21,12 +19,12 @@ export const getPropertyDetailsTool = (env: Env) =>
description:
"Returns metadata and configuration details about a GA4 property.",
inputSchema: z.object({ property: propertySchema }),
outputSchema: PropertyResponseSchema,
outputSchema: PropertySchema,
execute: async ({ context: args }) => {
const client = GaClient.fromEnv(env);
try {
const result = await client.getProperty(args.property);
return PropertyResponseSchema.parse({ response: result });
return PropertySchema.parse(result);
} catch (error) {
throw new Error(
`Failed to retrieve property details: ${error instanceof Error ? error.message : String(error)}`,
Expand All @@ -41,19 +39,17 @@ export const getCustomDimensionsAndMetricsTool = (env: Env) =>
description:
"Retrieves the custom dimensions and custom metrics configured for a GA4 property.",
inputSchema: z.object({ property: propertySchema }),
outputSchema: CustomDimensionsAndMetricsResponseSchema,
outputSchema: CustomDimensionsAndMetricsOutputSchema,
execute: async ({ context: args }) => {
const client = GaClient.fromEnv(env);
try {
// Both calls are independent — run in parallel to halve latency.
const [dimensions, metrics] = await Promise.all([
const [dimensionsResult, metricsResult] = await Promise.all([
client.listCustomDimensions(args.property),
client.listCustomMetrics(args.property),
]);

return CustomDimensionsAndMetricsResponseSchema.parse({
dimensions,
metrics,
return CustomDimensionsAndMetricsOutputSchema.parse({
...(dimensionsResult as object),
...(metricsResult as object),
});
} catch (error) {
throw new Error(
Expand Down
Loading
Loading