diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index b098e616a2..e36f351310 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -3600,6 +3600,25 @@ export const ResendIcon = (props: SVGProps) => ( ) +export const GoogleAnalyticsIcon = (props: SVGProps) => ( + + + + + + + +) + export const GoogleAdsIcon = (props: SVGProps) => ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 7bd422f585..6eea22053e 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -47,11 +47,12 @@ import { FirecrawlIcon, FirefliesIcon, GammaIcon, - GithubIcon, GitLabIcon, + GithubIcon, GmailIcon, GongIcon, GoogleAdsIcon, + GoogleAnalyticsIcon, GoogleBigQueryIcon, GoogleBooksIcon, GoogleCalendarIcon, @@ -91,9 +92,9 @@ import { LinkupIcon, LoopsIcon, LumaIcon, + MailServerIcon, MailchimpIcon, MailgunIcon, - MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -128,6 +129,8 @@ import { ResendIcon, RevenueCatIcon, S3Icon, + SQSIcon, + STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -139,19 +142,17 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, - SQSIcon, SshIcon, - STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, + TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, - TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -162,11 +163,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, - xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, + xIcon, } from '@/components/icons' type IconComponent = ComponentType> @@ -218,6 +219,7 @@ export const blockTypeToIconMap: Record = { gmail_v2: GmailIcon, gong: GongIcon, google_ads: GoogleAdsIcon, + google_analytics: GoogleAnalyticsIcon, google_bigquery: GoogleBigQueryIcon, google_books: GoogleBooksIcon, google_calendar_v2: GoogleCalendarIcon, diff --git a/apps/docs/content/docs/en/tools/firecrawl.mdx b/apps/docs/content/docs/en/tools/firecrawl.mdx index bbcc1d01f5..ee346b8e46 100644 --- a/apps/docs/content/docs/en/tools/firecrawl.mdx +++ b/apps/docs/content/docs/en/tools/firecrawl.mdx @@ -53,6 +53,9 @@ Extract structured content from web pages with comprehensive metadata support. C | `url` | string | Yes | The URL to scrape content from \(e.g., "https://example.com/page"\) | | `scrapeOptions` | json | No | Options for content scraping | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -86,6 +89,9 @@ Search for information on the web using Firecrawl | --------- | ---- | -------- | ----------- | | `query` | string | Yes | The search query to use | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -123,6 +129,9 @@ Crawl entire websites and extract structured content from all accessible pages | `includePaths` | json | No | URL paths to include in crawling \(e.g., \["/docs/*", "/api/*"\]\). Only these paths will be crawled | | `onlyMainContent` | boolean | No | Extract only main content from pages | | `apiKey` | string | Yes | Firecrawl API Key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -142,7 +151,6 @@ Crawl entire websites and extract structured content from all accessible pages | ↳ `statusCode` | number | HTTP status code | | ↳ `ogLocaleAlternate` | array | Alternate locale versions | | `total` | number | Total number of pages found during crawl | -| `creditsUsed` | number | Number of credits consumed by the crawl operation | ### `firecrawl_map` @@ -161,6 +169,9 @@ Get a complete list of URLs from any website quickly and reliably. Useful for di | `timeout` | number | No | Request timeout in milliseconds | | `location` | json | No | Geographic context for proxying \(country, languages\) | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -187,6 +198,9 @@ Extract structured data from entire webpages using natural language prompts and | `ignoreInvalidURLs` | boolean | No | Skip invalid URLs in the array \(default: true\) | | `scrapeOptions` | json | No | Advanced scraping configuration options | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -217,7 +231,6 @@ Autonomous web data extraction agent. Searches and gathers information based on | `success` | boolean | Whether the agent operation was successful | | `status` | string | Current status of the agent job \(processing, completed, failed\) | | `data` | object | Extracted data from the agent | -| `creditsUsed` | number | Number of credits consumed by this agent task | | `expiresAt` | string | Timestamp when the results expire \(24 hours\) | | `sources` | object | Array of source URLs used by the agent | diff --git a/apps/docs/content/docs/en/tools/google_analytics.mdx b/apps/docs/content/docs/en/tools/google_analytics.mdx new file mode 100644 index 0000000000..253f95a74a --- /dev/null +++ b/apps/docs/content/docs/en/tools/google_analytics.mdx @@ -0,0 +1,129 @@ +--- +title: Google Analytics +description: Query GA4 analytics data and reports +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Google Analytics](https://analytics.google.com) is Google's web and app analytics platform. Through Sim, your agents can query GA4 property data to automate reporting, monitoring, and analysis workflows. + +**The following Google Analytics Data API operations are included in this integration:** + +- **Run Report:** Generate customized reports with dimensions, metrics, date ranges, filters, and sorting. Supports pagination for large datasets. +- **Run Realtime Report:** Get live data from the last 30 minutes, including active users, page views, and conversions in real time. +- **Get Metadata:** Discover all available dimensions and metrics for a GA4 property, including their descriptions and categories. + +With these operations, your Sim agents can automate daily/weekly analytics reporting, monitor real-time traffic and conversions, build dashboards from GA4 data, detect anomalies in key metrics, and enrich workflows with analytics context—all without manual work in the Google Analytics UI. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Google Analytics GA4 into your workflow. Run custom reports, get realtime data, and discover available dimensions and metrics. + + + +## Tools + +### `google_analytics_run_report` + +Run a customized report on Google Analytics GA4 property data + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `propertyId` | string | Yes | The GA4 property ID \(e.g., 123456789\) | +| `dimensions` | string | Yes | Comma-separated dimension names \(e.g., date,country,deviceCategory\). See GA4 dimensions reference. | +| `metrics` | string | Yes | Comma-separated metric names \(e.g., activeUsers,sessions,screenPageViews\). See GA4 metrics reference. | +| `startDate` | string | Yes | Start date in YYYY-MM-DD format, or relative dates like "7daysAgo", "30daysAgo", "yesterday" | +| `endDate` | string | Yes | End date in YYYY-MM-DD format, or "today", "yesterday" | +| `dimensionFilter` | string | No | Dimension filter as JSON \(e.g., \{"filter":\{"fieldName":"country","stringFilter":\{"value":"US"\}\}\}\) | +| `metricFilter` | string | No | Metric filter as JSON \(e.g., \{"filter":\{"fieldName":"activeUsers","numericFilter":\{"operation":"GREATER_THAN","value":\{"int64Value":"100"\}\}\}\}\) | +| `orderBys` | string | No | Order by specification as JSON array \(e.g., \[\{"metric":\{"metricName":"activeUsers"\},"desc":true\}\]\) | +| `limit` | number | No | Maximum number of rows to return \(default: 10000, max: 250000\) | +| `offset` | number | No | Starting row offset for pagination \(default: 0\) | +| `keepEmptyRows` | boolean | No | Whether to include rows with all zero metric values | +| `currencyCode` | string | No | Currency code for revenue metrics \(e.g., USD, EUR\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `dimensionHeaders` | array | Dimension column headers | +| ↳ `name` | string | Dimension name | +| `metricHeaders` | array | Metric column headers | +| ↳ `name` | string | Metric name | +| ↳ `type` | string | Metric data type | +| `rows` | array | Report data rows | +| ↳ `dimensionValues` | json | Array of dimension values for this row | +| ↳ `metricValues` | json | Array of metric values for this row | +| `rowCount` | number | Total number of rows in the result | +| `metadata` | json | Report metadata including currency code and time zone | +| ↳ `currencyCode` | string | Currency code used in the report | +| ↳ `timeZone` | string | Time zone used in the report | + +### `google_analytics_run_realtime_report` + +Run a realtime report on Google Analytics GA4 property data from the last 30 minutes + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `propertyId` | string | Yes | The GA4 property ID \(e.g., 123456789\) | +| `dimensions` | string | No | Comma-separated dimension names for realtime data \(e.g., unifiedScreenName,country,deviceCategory\) | +| `metrics` | string | Yes | Comma-separated metric names \(e.g., activeUsers,screenPageViews,conversions\) | +| `dimensionFilter` | string | No | Dimension filter as JSON | +| `metricFilter` | string | No | Metric filter as JSON | +| `limit` | number | No | Maximum number of rows to return \(default: 10000, max: 250000\) | +| `startMinutesAgo` | number | No | Start of the time window in minutes ago \(default: 29, max: 29 for standard, 59 for 360\) | +| `endMinutesAgo` | number | No | End of the time window in minutes ago \(default: 0, meaning now\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `dimensionHeaders` | array | Dimension column headers | +| ↳ `name` | string | Dimension name | +| `metricHeaders` | array | Metric column headers | +| ↳ `name` | string | Metric name | +| ↳ `type` | string | Metric data type | +| `rows` | array | Realtime report data rows | +| ↳ `dimensionValues` | json | Array of dimension values for this row | +| ↳ `metricValues` | json | Array of metric values for this row | +| `rowCount` | number | Total number of rows in the result | + +### `google_analytics_get_metadata` + +Get available dimensions, metrics, and their descriptions for a Google Analytics GA4 property + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `propertyId` | string | Yes | The GA4 property ID \(e.g., 123456789\). Use 0 to get universal metadata available across all properties. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `dimensions` | array | Available dimensions for the property | +| ↳ `apiName` | string | API name to use in report requests | +| ↳ `uiName` | string | Human-readable display name | +| ↳ `description` | string | Description of the dimension | +| ↳ `category` | string | Category grouping | +| `metrics` | array | Available metrics for the property | +| ↳ `apiName` | string | API name to use in report requests | +| ↳ `uiName` | string | Human-readable display name | +| ↳ `description` | string | Description of the metric | +| ↳ `category` | string | Category grouping | +| ↳ `type` | string | Data type of the metric | + + diff --git a/apps/docs/content/docs/en/tools/google_books.mdx b/apps/docs/content/docs/en/tools/google_books.mdx index 543dd6f125..6ab604483d 100644 --- a/apps/docs/content/docs/en/tools/google_books.mdx +++ b/apps/docs/content/docs/en/tools/google_books.mdx @@ -46,6 +46,8 @@ Search for books using the Google Books API | `startIndex` | number | No | Index of the first result to return \(for pagination\) | | `maxResults` | number | No | Maximum number of results to return \(1-40\) | | `langRestrict` | string | No | Restrict results to a specific language \(ISO 639-1 code\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -82,6 +84,8 @@ Get detailed information about a specific book volume | `apiKey` | string | Yes | Google Books API key | | `volumeId` | string | Yes | The ID of the volume to retrieve | | `projection` | string | No | Projection level \(full, lite\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/google_maps.mdx b/apps/docs/content/docs/en/tools/google_maps.mdx index 459ffa7431..5439d99cbf 100644 --- a/apps/docs/content/docs/en/tools/google_maps.mdx +++ b/apps/docs/content/docs/en/tools/google_maps.mdx @@ -50,6 +50,8 @@ Get current air quality data for a location | `lat` | number | Yes | Latitude coordinate | | `lng` | number | Yes | Longitude coordinate | | `languageCode` | string | No | Language code for the response \(e.g., "en", "es"\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -91,6 +93,8 @@ Get directions and route information between two locations | `waypoints` | json | No | Array of intermediate waypoints | | `units` | string | No | Unit system: metric or imperial | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -135,6 +139,8 @@ Calculate travel distance and time between multiple origins and destinations | `avoid` | string | No | Features to avoid: tolls, highways, or ferries | | `units` | string | No | Unit system: metric or imperial | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -163,6 +169,8 @@ Get elevation data for a location | `apiKey` | string | Yes | Google Maps API key | | `lat` | number | Yes | Latitude coordinate | | `lng` | number | Yes | Longitude coordinate | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -185,6 +193,8 @@ Convert an address into geographic coordinates (latitude and longitude) | `address` | string | Yes | The address to geocode | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | | `region` | string | No | Region bias as a ccTLD code \(e.g., us, uk\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -217,6 +227,8 @@ Geolocate a device using WiFi access points, cell towers, or IP address | `considerIp` | boolean | No | Whether to use IP address for geolocation \(default: true\) | | `cellTowers` | array | No | Array of cell tower objects with cellId, locationAreaCode, mobileCountryCode, mobileNetworkCode | | `wifiAccessPoints` | array | No | Array of WiFi access point objects with macAddress \(required\), signalStrength, etc. | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -238,6 +250,8 @@ Get detailed information about a specific place | `placeId` | string | Yes | Google Place ID | | `fields` | string | No | Comma-separated list of fields to return | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -290,6 +304,8 @@ Search for places using a text query | `type` | string | No | Place type filter \(e.g., restaurant, cafe, hotel\) | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | | `region` | string | No | Region bias as a ccTLD code \(e.g., us, uk\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -322,6 +338,8 @@ Convert geographic coordinates (latitude and longitude) into a human-readable ad | `lat` | number | Yes | Latitude coordinate | | `lng` | number | Yes | Longitude coordinate | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -346,6 +364,8 @@ Snap GPS coordinates to the nearest road segment | `apiKey` | string | Yes | Google Maps API key with Roads API enabled | | `path` | string | Yes | Pipe-separated list of lat,lng coordinates \(e.g., "60.170880,24.942795\|60.170879,24.942796"\) | | `interpolate` | boolean | No | Whether to interpolate additional points along the road | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -399,6 +419,8 @@ Get timezone information for a location | `lng` | number | Yes | Longitude coordinate | | `timestamp` | number | No | Unix timestamp to determine DST offset \(defaults to current time\) | | `language` | string | No | Language code for timezone name \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -424,6 +446,8 @@ Validate and standardize a postal address | `regionCode` | string | No | ISO 3166-1 alpha-2 country code \(e.g., "US", "CA"\) | | `locality` | string | No | City or locality name | | `enableUspsCass` | boolean | No | Enable USPS CASS validation for US addresses | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/google_pagespeed.mdx b/apps/docs/content/docs/en/tools/google_pagespeed.mdx index 65b62e0e75..d2cd247c85 100644 --- a/apps/docs/content/docs/en/tools/google_pagespeed.mdx +++ b/apps/docs/content/docs/en/tools/google_pagespeed.mdx @@ -55,6 +55,8 @@ Analyze a webpage for performance, accessibility, SEO, and best practices using | `category` | string | No | Lighthouse categories to analyze \(comma-separated\): performance, accessibility, best-practices, seo | | `strategy` | string | No | Analysis strategy: desktop or mobile | | `locale` | string | No | Locale for results \(e.g., en, fr, de\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/google_translate.mdx b/apps/docs/content/docs/en/tools/google_translate.mdx index 8c0bd6bebb..a3b835f11b 100644 --- a/apps/docs/content/docs/en/tools/google_translate.mdx +++ b/apps/docs/content/docs/en/tools/google_translate.mdx @@ -43,6 +43,9 @@ Translate text between languages using the Google Cloud Translation API. Support | `target` | string | Yes | Target language code \(e.g., "es", "fr", "de", "ja"\) | | `source` | string | No | Source language code. If omitted, the API will auto-detect the source language. | | `format` | string | No | Format of the text: "text" for plain text, "html" for HTML content | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -61,6 +64,9 @@ Detect the language of text using the Google Cloud Translation API. | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Google Cloud API key with Cloud Translation API enabled | | `text` | string | Yes | The text to detect the language of | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/grain.mdx b/apps/docs/content/docs/en/tools/grain.mdx index 49e77f09a7..f0376177c5 100644 --- a/apps/docs/content/docs/en/tools/grain.mdx +++ b/apps/docs/content/docs/en/tools/grain.mdx @@ -138,6 +138,26 @@ Get the full transcript of a recording | ↳ `end` | number | End timestamp in ms | | ↳ `text` | string | Transcript text | +### `grain_list_views` + +List available Grain views for webhook subscriptions + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Grain API key \(Personal Access Token\) | +| `typeFilter` | string | No | Optional view type filter: recordings, highlights, or stories | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `views` | array | Array of Grain views | +| ↳ `id` | string | View UUID | +| ↳ `name` | string | View name | +| ↳ `type` | string | View type: recordings, highlights, or stories | + ### `grain_list_teams` List all teams in the workspace @@ -185,15 +205,9 @@ Create a webhook to receive recording events | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Grain API key \(Personal Access Token\) | | `hookUrl` | string | Yes | Webhook endpoint URL \(e.g., "https://example.com/webhooks/grain"\) | -| `hookType` | string | Yes | Type of webhook: "recording_added" or "upload_status" | -| `filterBeforeDatetime` | string | No | Filter: recordings before this ISO8601 date \(e.g., "2024-01-15T00:00:00Z"\) | -| `filterAfterDatetime` | string | No | Filter: recordings after this ISO8601 date \(e.g., "2024-01-01T00:00:00Z"\) | -| `filterParticipantScope` | string | No | Filter: "internal" or "external" | -| `filterTeamId` | string | No | Filter: specific team UUID \(e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"\) | -| `filterMeetingTypeId` | string | No | Filter: specific meeting type UUID \(e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"\) | -| `includeHighlights` | boolean | No | Include highlights in webhook payload | -| `includeParticipants` | boolean | No | Include participants in webhook payload | -| `includeAiSummary` | boolean | No | Include AI summary in webhook payload | +| `viewId` | string | Yes | Grain view ID from GET /_/public-api/views | +| `actions` | array | No | Optional list of actions to subscribe to: added, updated, removed | +| `items` | string | No | No description | #### Output @@ -202,9 +216,8 @@ Create a webhook to receive recording events | `id` | string | Hook UUID | | `enabled` | boolean | Whether hook is active | | `hook_url` | string | The webhook URL | -| `hook_type` | string | Type of hook: recording_added or upload_status | -| `filter` | object | Applied filters | -| `include` | object | Included fields | +| `view_id` | string | Grain view ID for the webhook | +| `actions` | array | Configured actions for the webhook | | `inserted_at` | string | ISO8601 creation timestamp | ### `grain_list_hooks` @@ -225,9 +238,8 @@ List all webhooks for the account | ↳ `id` | string | Hook UUID | | ↳ `enabled` | boolean | Whether hook is active | | ↳ `hook_url` | string | Webhook URL | -| ↳ `hook_type` | string | Type: recording_added or upload_status | -| ↳ `filter` | object | Applied filters | -| ↳ `include` | object | Included fields | +| ↳ `view_id` | string | Grain view ID | +| ↳ `actions` | array | Configured actions | | ↳ `inserted_at` | string | Creation timestamp | ### `grain_delete_hook` diff --git a/apps/docs/content/docs/en/tools/jina.mdx b/apps/docs/content/docs/en/tools/jina.mdx index 9a2561c0da..d04cf7d71a 100644 --- a/apps/docs/content/docs/en/tools/jina.mdx +++ b/apps/docs/content/docs/en/tools/jina.mdx @@ -64,6 +64,7 @@ Extract and process web content into clean, LLM-friendly text using Jina AI Read | Parameter | Type | Description | | --------- | ---- | ----------- | | `content` | string | The extracted content from the URL, processed into clean, LLM-friendly text | +| `tokensUsed` | number | Number of Jina tokens consumed by this request | ### `jina_search` @@ -97,5 +98,6 @@ Search the web and return top 5 results with LLM-friendly content. Each result i | ↳ `content` | string | LLM-friendly extracted content | | ↳ `usage` | object | Token usage information | | ↳ `tokens` | number | Number of tokens consumed by this request | +| `tokensUsed` | number | Number of Jina tokens consumed by this request | diff --git a/apps/docs/content/docs/en/tools/linkup.mdx b/apps/docs/content/docs/en/tools/linkup.mdx index c3812f1d5b..62166b3542 100644 --- a/apps/docs/content/docs/en/tools/linkup.mdx +++ b/apps/docs/content/docs/en/tools/linkup.mdx @@ -51,6 +51,9 @@ Search the web for information using Linkup | `includeDomains` | string | No | Comma-separated list of domain names to restrict search results to | | `includeInlineCitations` | boolean | No | Add inline citations to answers \(only applies when outputType is "sourcedAnswer"\) | | `includeSources` | boolean | No | Include sources in response | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 6f25b64ffe..131bf3a344 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -47,6 +47,7 @@ "gmail", "gong", "google_ads", + "google_analytics", "google_bigquery", "google_books", "google_calendar", @@ -167,4 +168,4 @@ "zep", "zoom" ] -} +} \ No newline at end of file diff --git a/apps/docs/content/docs/en/tools/perplexity.mdx b/apps/docs/content/docs/en/tools/perplexity.mdx index 75efe7a487..1c98cb791e 100644 --- a/apps/docs/content/docs/en/tools/perplexity.mdx +++ b/apps/docs/content/docs/en/tools/perplexity.mdx @@ -49,6 +49,9 @@ Generate completions using Perplexity AI chat models | `max_tokens` | number | No | Maximum number of tokens to generate \(e.g., 1024, 2048, 4096\) | | `temperature` | number | No | Sampling temperature between 0 and 1 \(e.g., 0.0 for deterministic, 0.7 for creative\) | | `apiKey` | string | Yes | Perplexity API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -78,6 +81,8 @@ Get ranked search results from Perplexity | `search_after_date` | string | No | Include only content published after this date \(format: MM/DD/YYYY\) | | `search_before_date` | string | No | Include only content published before this date \(format: MM/DD/YYYY\) | | `apiKey` | string | Yes | Perplexity API key | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/serper.mdx b/apps/docs/content/docs/en/tools/serper.mdx index ccc21f34e6..f025f0e453 100644 --- a/apps/docs/content/docs/en/tools/serper.mdx +++ b/apps/docs/content/docs/en/tools/serper.mdx @@ -47,6 +47,9 @@ A powerful web search tool that provides access to Google search results through | `hl` | string | No | Language code for search results \(e.g., "en", "es", "de", "fr"\) | | `type` | string | No | Type of search to perform \(e.g., "search", "news", "images", "videos", "places", "shopping"\) | | `apiKey` | string | Yes | Serper API Key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/sim/blocks/blocks/google_analytics.ts b/apps/sim/blocks/blocks/google_analytics.ts new file mode 100644 index 0000000000..173bbb758f --- /dev/null +++ b/apps/sim/blocks/blocks/google_analytics.ts @@ -0,0 +1,301 @@ +import { GoogleAnalyticsIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' +import { getScopesForService } from '@/lib/oauth/utils' +import type { GoogleAnalyticsResponse } from '@/tools/google_analytics/types' + +export const GoogleAnalyticsBlock: BlockConfig = { + type: 'google_analytics', + name: 'Google Analytics', + description: 'Query GA4 analytics data and reports', + authMode: AuthMode.OAuth, + longDescription: + 'Integrate Google Analytics GA4 into your workflow. Run custom reports, get realtime data, and discover available dimensions and metrics.', + docsLink: 'https://docs.sim.ai/tools/google_analytics', + category: 'tools', + bgColor: '#E0E0E0', + icon: GoogleAnalyticsIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Run Report', id: 'run_report' }, + { label: 'Run Realtime Report', id: 'run_realtime_report' }, + { label: 'Get Metadata', id: 'get_metadata' }, + ], + value: () => 'run_report', + }, + { + id: 'credential', + title: 'Google Analytics Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + required: true, + serviceId: 'google-analytics', + requiredScopes: getScopesForService('google-analytics'), + placeholder: 'Select Google Analytics account', + }, + { + id: 'manualCredential', + title: 'Google Analytics Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + { + id: 'propertyId', + title: 'Property ID', + type: 'short-input', + placeholder: 'GA4 property ID (e.g., 123456789)', + required: true, + }, + { + id: 'metrics', + title: 'Metrics', + type: 'short-input', + placeholder: 'e.g., activeUsers,sessions,screenPageViews', + condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] }, + required: { field: 'operation', value: ['run_report', 'run_realtime_report'] }, + wandConfig: { + enabled: true, + prompt: `Generate a comma-separated list of GA4 metric API names based on the user's description. +Common metrics: activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conversions, totalRevenue, newUsers, engagedSessions, engagementRate, eventsPerSession, eventCount, totalUsers. +Return ONLY the comma-separated metric names - no explanations, no extra text.`, + placeholder: 'Describe the metrics you want...', + }, + }, + { + id: 'dimensions', + title: 'Dimensions', + type: 'short-input', + placeholder: 'e.g., date,country,deviceCategory', + condition: { field: 'operation', value: 'run_report' }, + required: { field: 'operation', value: 'run_report' }, + wandConfig: { + enabled: true, + prompt: `Generate a comma-separated list of GA4 dimension API names based on the user's description. +Common dimensions: date, country, city, deviceCategory, browser, operatingSystem, sessionSource, sessionMedium, sessionCampaignName, pagePath, pageTitle, landingPage, language, newVsReturning. +Return ONLY the comma-separated dimension names - no explanations, no extra text.`, + placeholder: 'Describe the dimensions you want...', + }, + }, + { + id: 'realtimeDimensions', + title: 'Dimensions', + type: 'short-input', + placeholder: 'e.g., unifiedScreenName,country (optional)', + condition: { field: 'operation', value: 'run_realtime_report' }, + wandConfig: { + enabled: true, + prompt: `Generate a comma-separated list of GA4 realtime dimension API names. +Common realtime dimensions: unifiedScreenName, country, city, deviceCategory, platform, streamName, audienceName. +Return ONLY the comma-separated dimension names - no explanations, no extra text.`, + placeholder: 'Describe the dimensions you want...', + }, + }, + { + id: 'startDate', + title: 'Start Date', + type: 'short-input', + placeholder: 'e.g., 7daysAgo, 30daysAgo, 2024-01-01', + condition: { field: 'operation', value: 'run_report' }, + required: { field: 'operation', value: 'run_report' }, + wandConfig: { + enabled: true, + prompt: 'Generate a GA4 date string. Supported formats: YYYY-MM-DD, or relative dates like "today", "yesterday", "NdaysAgo" (e.g., "7daysAgo", "30daysAgo"). Return ONLY the date string.', + generationType: 'timestamp', + }, + }, + { + id: 'endDate', + title: 'End Date', + type: 'short-input', + placeholder: 'e.g., today, yesterday, 2024-12-31', + condition: { field: 'operation', value: 'run_report' }, + required: { field: 'operation', value: 'run_report' }, + wandConfig: { + enabled: true, + prompt: 'Generate a GA4 date string. Supported formats: YYYY-MM-DD, or relative dates like "today", "yesterday", "NdaysAgo" (e.g., "7daysAgo", "30daysAgo"). Return ONLY the date string.', + generationType: 'timestamp', + }, + }, + { + id: 'dimensionFilter', + title: 'Dimension Filter', + type: 'long-input', + placeholder: 'JSON filter (e.g., {"filter":{"fieldName":"country","stringFilter":{"value":"US"}}})', + condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a GA4 dimension filter JSON object. +Format: {"filter":{"fieldName":"dimensionName","stringFilter":{"matchType":"EXACT","value":"filterValue"}}} +For multiple filters use: {"andGroup":{"expressions":[...]}} or {"orGroup":{"expressions":[...]}} +Match types: EXACT, BEGINS_WITH, ENDS_WITH, CONTAINS, FULL_REGEXP, PARTIAL_REGEXP. +Return ONLY valid JSON - no explanations.`, + generationType: 'json-object', + placeholder: 'Describe how to filter dimensions...', + }, + }, + { + id: 'metricFilter', + title: 'Metric Filter', + type: 'long-input', + placeholder: 'JSON filter for metrics', + condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a GA4 metric filter JSON object. +Format: {"filter":{"fieldName":"metricName","numericFilter":{"operation":"GREATER_THAN","value":{"int64Value":"100"}}}} +Operations: EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL. +Return ONLY valid JSON - no explanations.`, + generationType: 'json-object', + placeholder: 'Describe how to filter metrics...', + }, + }, + { + id: 'orderBys', + title: 'Order By', + type: 'long-input', + placeholder: 'JSON array (e.g., [{"metric":{"metricName":"activeUsers"},"desc":true}])', + condition: { field: 'operation', value: 'run_report' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a GA4 orderBys JSON array. +Format for metric sort: [{"metric":{"metricName":"metricName"},"desc":true}] +Format for dimension sort: [{"dimension":{"dimensionName":"dimensionName"},"desc":false}] +Return ONLY valid JSON array - no explanations.`, + generationType: 'json-object', + placeholder: 'Describe how to sort results...', + }, + }, + { + id: 'limit', + title: 'Row Limit', + type: 'short-input', + placeholder: 'Max rows to return (default: 10000)', + condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] }, + mode: 'advanced', + }, + { + id: 'offset', + title: 'Row Offset', + type: 'short-input', + placeholder: 'Starting row for pagination (default: 0)', + condition: { field: 'operation', value: 'run_report' }, + mode: 'advanced', + }, + { + id: 'startMinutesAgo', + title: 'Start Minutes Ago', + type: 'short-input', + placeholder: 'Start of time window in minutes ago (default: 29)', + condition: { field: 'operation', value: 'run_realtime_report' }, + mode: 'advanced', + }, + { + id: 'endMinutesAgo', + title: 'End Minutes Ago', + type: 'short-input', + placeholder: 'End of time window in minutes ago (default: 0)', + condition: { field: 'operation', value: 'run_realtime_report' }, + mode: 'advanced', + }, + { + id: 'keepEmptyRows', + title: 'Keep Empty Rows', + type: 'dropdown', + options: [ + { label: 'No (default)', id: '' }, + { label: 'Yes', id: 'true' }, + ], + placeholder: 'Include rows with all zero metric values', + condition: { field: 'operation', value: 'run_report' }, + mode: 'advanced', + }, + { + id: 'currencyCode', + title: 'Currency Code', + type: 'short-input', + placeholder: 'e.g., USD, EUR', + condition: { field: 'operation', value: 'run_report' }, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'google_analytics_run_report', + 'google_analytics_run_realtime_report', + 'google_analytics_get_metadata', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'run_report': + return 'google_analytics_run_report' + case 'run_realtime_report': + return 'google_analytics_run_realtime_report' + case 'get_metadata': + return 'google_analytics_get_metadata' + default: + throw new Error(`Invalid Google Analytics operation: ${params.operation}`) + } + }, + params: (params) => { + const { oauthCredential, realtimeDimensions, keepEmptyRows, ...rest } = params + + return { + oauthCredential, + ...rest, + dimensions: params.operation === 'run_realtime_report' + ? realtimeDimensions + : rest.dimensions, + limit: rest.limit ? Number.parseInt(rest.limit as string, 10) : undefined, + offset: rest.offset ? Number.parseInt(rest.offset as string, 10) : undefined, + startMinutesAgo: rest.startMinutesAgo + ? Number.parseInt(rest.startMinutesAgo as string, 10) + : undefined, + endMinutesAgo: rest.endMinutesAgo + ? Number.parseInt(rest.endMinutesAgo as string, 10) + : undefined, + keepEmptyRows: keepEmptyRows === 'true' ? true : undefined, + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Google Analytics access token' }, + propertyId: { type: 'string', description: 'GA4 property ID' }, + metrics: { type: 'string', description: 'Comma-separated metric names' }, + dimensions: { type: 'string', description: 'Comma-separated dimension names' }, + realtimeDimensions: { type: 'string', description: 'Comma-separated realtime dimension names' }, + startDate: { type: 'string', description: 'Report start date' }, + endDate: { type: 'string', description: 'Report end date' }, + dimensionFilter: { type: 'string', description: 'Dimension filter JSON' }, + metricFilter: { type: 'string', description: 'Metric filter JSON' }, + orderBys: { type: 'string', description: 'Order by specification JSON' }, + limit: { type: 'string', description: 'Maximum rows to return' }, + offset: { type: 'string', description: 'Starting row offset' }, + startMinutesAgo: { type: 'string', description: 'Realtime start minutes ago' }, + endMinutesAgo: { type: 'string', description: 'Realtime end minutes ago' }, + keepEmptyRows: { type: 'string', description: 'Include rows with all zero metric values' }, + currencyCode: { type: 'string', description: 'Currency code for revenue metrics' }, + }, + outputs: { + dimensionHeaders: { type: 'json', description: 'Dimension column headers' }, + metricHeaders: { type: 'json', description: 'Metric column headers' }, + rows: { type: 'json', description: 'Report data rows' }, + rowCount: { type: 'number', description: 'Total number of rows' }, + metadata: { type: 'json', description: 'Report metadata (currency, timezone)' }, + dimensions: { type: 'json', description: 'Available dimensions (from get_metadata)' }, + metrics: { type: 'json', description: 'Available metrics (from get_metadata)' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index ee75b89354..b2829a1739 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -53,6 +53,7 @@ import { GmailBlock, GmailV2Block } from '@/blocks/blocks/gmail' import { GongBlock } from '@/blocks/blocks/gong' import { GoogleSearchBlock } from '@/blocks/blocks/google' import { GoogleAdsBlock } from '@/blocks/blocks/google_ads' +import { GoogleAnalyticsBlock } from '@/blocks/blocks/google_analytics' import { GoogleBigQueryBlock } from '@/blocks/blocks/google_bigquery' import { GoogleBooksBlock } from '@/blocks/blocks/google_books' import { GoogleCalendarBlock, GoogleCalendarV2Block } from '@/blocks/blocks/google_calendar' @@ -261,6 +262,7 @@ export const registry: Record = { google_calendar: GoogleCalendarBlock, google_calendar_v2: GoogleCalendarV2Block, google_ads: GoogleAdsBlock, + google_analytics: GoogleAnalyticsBlock, google_books: GoogleBooksBlock, google_contacts: GoogleContactsBlock, google_docs: GoogleDocsBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index b098e616a2..e36f351310 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -3600,6 +3600,25 @@ export const ResendIcon = (props: SVGProps) => ( ) +export const GoogleAnalyticsIcon = (props: SVGProps) => ( + + + + + + + +) + export const GoogleAdsIcon = (props: SVGProps) => ( diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index eaa2bc4fe0..7ec93f9c6d 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -970,6 +970,41 @@ export const auth = betterAuth({ } }, }, + { + providerId: 'google-analytics', + clientId: env.GOOGLE_CLIENT_ID as string, + clientSecret: env.GOOGLE_CLIENT_SECRET as string, + discoveryUrl: 'https://accounts.google.com/.well-known/openid-configuration', + accessType: 'offline', + scopes: getCanonicalScopesForProvider('google-analytics'), + prompt: 'consent', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-analytics`, + getUserInfo: async (tokens) => { + try { + const response = await fetch('https://openidconnect.googleapis.com/v1/userinfo', { + headers: { Authorization: `Bearer ${tokens.accessToken}` }, + }) + if (\!response.ok) { + logger.error('Failed to fetch Google user info', { status: response.status }) + throw new Error(`Failed to fetch Google user info: ${response.statusText}`) + } + const profile = await response.json() + const now = new Date() + return { + id: `${profile.sub}-${crypto.randomUUID()}`, + name: profile.name || 'Google User', + email: profile.email, + image: profile.picture || undefined, + emailVerified: profile.email_verified || false, + createdAt: now, + updatedAt: now, + } + } catch (error) { + logger.error('Error in Google getUserInfo', { error }) + throw error + } + }, + }, { providerId: 'google-ads', clientId: env.GOOGLE_CLIENT_ID as string, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 73a71a3e32..fcc49f8cf0 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -8,6 +8,7 @@ import { DropboxIcon, GmailIcon, GoogleAdsIcon, + GoogleAnalyticsIcon, GoogleBigQueryIcon, GoogleCalendarIcon, GoogleContactsIcon, @@ -147,6 +148,18 @@ export const OAUTH_PROVIDERS: Record = { 'https://www.googleapis.com/auth/contacts', ], }, + 'google-analytics': { + name: 'Google Analytics', + description: 'Query GA4 analytics data, run reports, and get realtime metrics.', + providerId: 'google-analytics', + icon: GoogleAnalyticsIcon, + baseProviderIcon: GoogleIcon, + scopes: [ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/analytics.readonly', + ], + }, 'google-ads': { name: 'Google Ads', description: 'Query campaigns, ad groups, and performance metrics in Google Ads.', diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 7e53aaecb3..7e503641a4 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -9,6 +9,7 @@ export type OAuthProvider = | 'google-calendar' | 'google-contacts' | 'google-ads' + | 'google-analytics' | 'google-bigquery' | 'google-tasks' | 'google-vault' @@ -57,6 +58,7 @@ export type OAuthService = | 'google-calendar' | 'google-contacts' | 'google-ads' + | 'google-analytics' | 'google-bigquery' | 'google-tasks' | 'google-vault' diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 5ca4263919..15d86d0497 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -24,6 +24,7 @@ export const SCOPE_DESCRIPTIONS: Record = { 'https://www.googleapis.com/auth/userinfo.profile': 'View basic profile info', 'https://www.googleapis.com/auth/forms.body': 'View and manage Google Forms', 'https://www.googleapis.com/auth/forms.responses.readonly': 'View responses to Google Forms', + 'https://www.googleapis.com/auth/analytics.readonly': 'Read-only access to Google Analytics data', 'https://www.googleapis.com/auth/adwords': 'Manage Google Ads campaigns and reporting', 'https://www.googleapis.com/auth/bigquery': 'View and manage data in Google BigQuery', 'https://www.googleapis.com/auth/ediscovery': 'Access Google Vault for eDiscovery', diff --git a/apps/sim/tools/google_analytics/get_metadata.ts b/apps/sim/tools/google_analytics/get_metadata.ts new file mode 100644 index 0000000000..826d4b528d --- /dev/null +++ b/apps/sim/tools/google_analytics/get_metadata.ts @@ -0,0 +1,113 @@ +import type { ToolConfig } from '@/tools/types' +import type { + GoogleAnalyticsGetMetadataParams, + GoogleAnalyticsGetMetadataResponse, +} from '@/tools/google_analytics/types' + +export const getMetadataTool: ToolConfig< + GoogleAnalyticsGetMetadataParams, + GoogleAnalyticsGetMetadataResponse +> = { + id: 'google_analytics_get_metadata', + name: 'Get Google Analytics Metadata', + description: + 'Get available dimensions, metrics, and their descriptions for a Google Analytics GA4 property', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-analytics', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the Google Analytics API', + }, + propertyId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'The GA4 property ID (e.g., 123456789). Use 0 to get universal metadata available across all properties.', + }, + }, + + request: { + url: (params) => + `https://analyticsdata.googleapis.com/v1beta/properties/${params.propertyId.trim()}/metadata`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || 'Failed to get Google Analytics metadata') + } + + return { + success: true, + output: { + dimensions: (data.dimensions ?? []).map( + (d: { apiName: string; uiName: string; description: string; category: string }) => ({ + apiName: d.apiName ?? '', + uiName: d.uiName ?? '', + description: d.description ?? '', + category: d.category ?? '', + }) + ), + metrics: (data.metrics ?? []).map( + (m: { + apiName: string + uiName: string + description: string + category: string + type: string + }) => ({ + apiName: m.apiName ?? '', + uiName: m.uiName ?? '', + description: m.description ?? '', + category: m.category ?? '', + type: m.type ?? '', + }) + ), + }, + } + }, + + outputs: { + dimensions: { + type: 'array', + description: 'Available dimensions for the property', + items: { + type: 'object', + properties: { + apiName: { type: 'string', description: 'API name to use in report requests' }, + uiName: { type: 'string', description: 'Human-readable display name' }, + description: { type: 'string', description: 'Description of the dimension' }, + category: { type: 'string', description: 'Category grouping' }, + }, + }, + }, + metrics: { + type: 'array', + description: 'Available metrics for the property', + items: { + type: 'object', + properties: { + apiName: { type: 'string', description: 'API name to use in report requests' }, + uiName: { type: 'string', description: 'Human-readable display name' }, + description: { type: 'string', description: 'Description of the metric' }, + category: { type: 'string', description: 'Category grouping' }, + type: { type: 'string', description: 'Data type of the metric' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/google_analytics/index.ts b/apps/sim/tools/google_analytics/index.ts new file mode 100644 index 0000000000..00106476c1 --- /dev/null +++ b/apps/sim/tools/google_analytics/index.ts @@ -0,0 +1,9 @@ +import { getMetadataTool } from '@/tools/google_analytics/get_metadata' +import { runRealtimeReportTool } from '@/tools/google_analytics/run_realtime_report' +import { runReportTool } from '@/tools/google_analytics/run_report' + +export const googleAnalyticsRunReportTool = runReportTool +export const googleAnalyticsRunRealtimeReportTool = runRealtimeReportTool +export const googleAnalyticsGetMetadataTool = getMetadataTool + +export * from './types' diff --git a/apps/sim/tools/google_analytics/run_realtime_report.ts b/apps/sim/tools/google_analytics/run_realtime_report.ts new file mode 100644 index 0000000000..b42e4b928c --- /dev/null +++ b/apps/sim/tools/google_analytics/run_realtime_report.ts @@ -0,0 +1,185 @@ +import type { ToolConfig } from '@/tools/types' +import type { + GoogleAnalyticsRunRealtimeReportParams, + GoogleAnalyticsRunRealtimeReportResponse, +} from '@/tools/google_analytics/types' + +export const runRealtimeReportTool: ToolConfig< + GoogleAnalyticsRunRealtimeReportParams, + GoogleAnalyticsRunRealtimeReportResponse +> = { + id: 'google_analytics_run_realtime_report', + name: 'Run Google Analytics Realtime Report', + description: 'Run a realtime report on Google Analytics GA4 property data from the last 30 minutes', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-analytics', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the Google Analytics API', + }, + propertyId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The GA4 property ID (e.g., 123456789)', + }, + dimensions: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated dimension names for realtime data (e.g., unifiedScreenName,country,deviceCategory)', + }, + metrics: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Comma-separated metric names (e.g., activeUsers,screenPageViews,conversions)', + }, + dimensionFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Dimension filter as JSON', + }, + metricFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Metric filter as JSON', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of rows to return (default: 10000, max: 250000)', + }, + startMinutesAgo: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Start of the time window in minutes ago (default: 29, max: 29 for standard, 59 for 360)', + }, + endMinutesAgo: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'End of the time window in minutes ago (default: 0, meaning now)', + }, + }, + + request: { + url: (params) => + `https://analyticsdata.googleapis.com/v1beta/properties/${params.propertyId.trim()}:runRealtimeReport`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + metrics: params.metrics + .split(',') + .map((m: string) => ({ name: m.trim() })), + } + + if (params.dimensions) { + body.dimensions = params.dimensions + .split(',') + .map((d: string) => ({ name: d.trim() })) + } + if (params.dimensionFilter) { + body.dimensionFilter = JSON.parse(params.dimensionFilter) + } + if (params.metricFilter) { + body.metricFilter = JSON.parse(params.metricFilter) + } + if (params.limit !== undefined) { + body.limit = params.limit + } + if (params.startMinutesAgo !== undefined || params.endMinutesAgo !== undefined) { + body.minuteRanges = [ + { + startMinutesAgo: params.startMinutesAgo ?? 29, + endMinutesAgo: params.endMinutesAgo ?? 0, + }, + ] + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || 'Failed to run Google Analytics realtime report') + } + + return { + success: true, + output: { + dimensionHeaders: data.dimensionHeaders ?? [], + metricHeaders: data.metricHeaders ?? [], + rows: data.rows ?? [], + rowCount: data.rowCount ?? null, + }, + } + }, + + outputs: { + dimensionHeaders: { + type: 'array', + description: 'Dimension column headers', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Dimension name' }, + }, + }, + }, + metricHeaders: { + type: 'array', + description: 'Metric column headers', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Metric name' }, + type: { type: 'string', description: 'Metric data type' }, + }, + }, + }, + rows: { + type: 'array', + description: 'Realtime report data rows', + items: { + type: 'object', + properties: { + dimensionValues: { + type: 'json', + description: 'Array of dimension values for this row', + }, + metricValues: { + type: 'json', + description: 'Array of metric values for this row', + }, + }, + }, + }, + rowCount: { + type: 'number', + description: 'Total number of rows in the result', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/google_analytics/run_report.ts b/apps/sim/tools/google_analytics/run_report.ts new file mode 100644 index 0000000000..03f2a98380 --- /dev/null +++ b/apps/sim/tools/google_analytics/run_report.ts @@ -0,0 +1,230 @@ +import type { ToolConfig } from '@/tools/types' +import type { + GoogleAnalyticsRunReportParams, + GoogleAnalyticsRunReportResponse, +} from '@/tools/google_analytics/types' + +export const runReportTool: ToolConfig< + GoogleAnalyticsRunReportParams, + GoogleAnalyticsRunReportResponse +> = { + id: 'google_analytics_run_report', + name: 'Run Google Analytics Report', + description: 'Run a customized report on Google Analytics GA4 property data', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-analytics', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the Google Analytics API', + }, + propertyId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The GA4 property ID (e.g., 123456789)', + }, + dimensions: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Comma-separated dimension names (e.g., date,country,deviceCategory). See GA4 dimensions reference.', + }, + metrics: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Comma-separated metric names (e.g., activeUsers,sessions,screenPageViews). See GA4 metrics reference.', + }, + startDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Start date in YYYY-MM-DD format, or relative dates like "7daysAgo", "30daysAgo", "yesterday"', + }, + endDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'End date in YYYY-MM-DD format, or "today", "yesterday"', + }, + dimensionFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Dimension filter as JSON (e.g., {"filter":{"fieldName":"country","stringFilter":{"value":"US"}}})', + }, + metricFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Metric filter as JSON (e.g., {"filter":{"fieldName":"activeUsers","numericFilter":{"operation":"GREATER_THAN","value":{"int64Value":"100"}}}})', + }, + orderBys: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Order by specification as JSON array (e.g., [{"metric":{"metricName":"activeUsers"},"desc":true}])', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of rows to return (default: 10000, max: 250000)', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Starting row offset for pagination (default: 0)', + }, + keepEmptyRows: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include rows with all zero metric values', + }, + currencyCode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Currency code for revenue metrics (e.g., USD, EUR)', + }, + }, + + request: { + url: (params) => + `https://analyticsdata.googleapis.com/v1beta/properties/${params.propertyId.trim()}:runReport`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + dimensions: params.dimensions + .split(',') + .map((d: string) => ({ name: d.trim() })), + metrics: params.metrics + .split(',') + .map((m: string) => ({ name: m.trim() })), + dateRanges: [{ startDate: params.startDate, endDate: params.endDate }], + } + + if (params.dimensionFilter) { + body.dimensionFilter = JSON.parse(params.dimensionFilter) + } + if (params.metricFilter) { + body.metricFilter = JSON.parse(params.metricFilter) + } + if (params.orderBys) { + body.orderBys = JSON.parse(params.orderBys) + } + if (params.limit !== undefined) { + body.limit = params.limit + } + if (params.offset !== undefined) { + body.offset = params.offset + } + if (params.keepEmptyRows !== undefined) { + body.keepEmptyRows = params.keepEmptyRows + } + if (params.currencyCode) { + body.currencyCode = params.currencyCode + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || 'Failed to run Google Analytics report') + } + + return { + success: true, + output: { + dimensionHeaders: data.dimensionHeaders ?? [], + metricHeaders: data.metricHeaders ?? [], + rows: data.rows ?? [], + rowCount: data.rowCount ?? null, + metadata: data.metadata + ? { + currencyCode: data.metadata.currencyCode ?? null, + timeZone: data.metadata.timeZone ?? null, + } + : null, + }, + } + }, + + outputs: { + dimensionHeaders: { + type: 'array', + description: 'Dimension column headers', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Dimension name' }, + }, + }, + }, + metricHeaders: { + type: 'array', + description: 'Metric column headers', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Metric name' }, + type: { type: 'string', description: 'Metric data type' }, + }, + }, + }, + rows: { + type: 'array', + description: 'Report data rows', + items: { + type: 'object', + properties: { + dimensionValues: { + type: 'json', + description: 'Array of dimension values for this row', + }, + metricValues: { + type: 'json', + description: 'Array of metric values for this row', + }, + }, + }, + }, + rowCount: { + type: 'number', + description: 'Total number of rows in the result', + optional: true, + }, + metadata: { + type: 'json', + description: 'Report metadata including currency code and time zone', + optional: true, + properties: { + currencyCode: { type: 'string', description: 'Currency code used in the report' }, + timeZone: { type: 'string', description: 'Time zone used in the report' }, + }, + }, + }, +} diff --git a/apps/sim/tools/google_analytics/types.ts b/apps/sim/tools/google_analytics/types.ts new file mode 100644 index 0000000000..50cc3ad4c9 --- /dev/null +++ b/apps/sim/tools/google_analytics/types.ts @@ -0,0 +1,97 @@ +import type { ToolResponse } from '@/tools/types' + +export interface GoogleAnalyticsRunReportParams { + accessToken: string + propertyId: string + dimensions: string + metrics: string + startDate: string + endDate: string + dimensionFilter?: string + metricFilter?: string + orderBys?: string + limit?: number + offset?: number + keepEmptyRows?: boolean + currencyCode?: string +} + +export interface GoogleAnalyticsRunRealtimeReportParams { + accessToken: string + propertyId: string + dimensions?: string + metrics: string + dimensionFilter?: string + metricFilter?: string + limit?: number + startMinutesAgo?: number + endMinutesAgo?: number +} + +export interface GoogleAnalyticsGetMetadataParams { + accessToken: string + propertyId: string +} + +export interface GoogleAnalyticsDimensionHeader { + name: string +} + +export interface GoogleAnalyticsMetricHeader { + name: string + type: string +} + +export interface GoogleAnalyticsRow { + dimensionValues: Array<{ value: string }> + metricValues: Array<{ value: string }> +} + +export interface GoogleAnalyticsRunReportResponse extends ToolResponse { + output: { + dimensionHeaders: GoogleAnalyticsDimensionHeader[] + metricHeaders: GoogleAnalyticsMetricHeader[] + rows: GoogleAnalyticsRow[] + rowCount: number | null + metadata: { + currencyCode: string | null + timeZone: string | null + } | null + } +} + +export interface GoogleAnalyticsRunRealtimeReportResponse extends ToolResponse { + output: { + dimensionHeaders: GoogleAnalyticsDimensionHeader[] + metricHeaders: GoogleAnalyticsMetricHeader[] + rows: GoogleAnalyticsRow[] + rowCount: number | null + } +} + +export interface GoogleAnalyticsDimensionMetadata { + apiName: string + uiName: string + description: string + category: string +} + +export interface GoogleAnalyticsMetricMetadata { + apiName: string + uiName: string + description: string + category: string + type: string +} + +export interface GoogleAnalyticsGetMetadataResponse extends ToolResponse { + output: { + dimensions: GoogleAnalyticsDimensionMetadata[] + metrics: GoogleAnalyticsMetricMetadata[] + } +} + +export type GoogleAnalyticsResponse = + | GoogleAnalyticsRunReportResponse + | GoogleAnalyticsRunRealtimeReportResponse + | GoogleAnalyticsGetMetadataResponse diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index f14ff024eb..988bf37392 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -797,6 +797,11 @@ import { googleDriveUpdateTool, googleDriveUploadTool, } from '@/tools/google_drive' +import { + googleAnalyticsGetMetadataTool, + googleAnalyticsRunRealtimeReportTool, + googleAnalyticsRunReportTool, +} from '@/tools/google_analytics' import { googleFormsBatchUpdateTool, googleFormsCreateFormTool, @@ -3305,6 +3310,9 @@ export const tools: Record = { revenuecat_defer_google_subscription: revenuecatDeferGoogleSubscriptionTool, revenuecat_refund_google_subscription: revenuecatRefundGoogleSubscriptionTool, revenuecat_revoke_google_subscription: revenuecatRevokeGoogleSubscriptionTool, + google_analytics_run_report: googleAnalyticsRunReportTool, + google_analytics_run_realtime_report: googleAnalyticsRunRealtimeReportTool, + google_analytics_get_metadata: googleAnalyticsGetMetadataTool, google_drive_copy: googleDriveCopyTool, google_drive_create_folder: googleDriveCreateFolderTool, google_drive_delete: googleDriveDeleteTool,