Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ exports[`classifies every Google service for bundle OAuth UX 1`] = `
"id": "google-search-console",
"oauthAudience": "standard-user",
},
{
"id": "google-analytics-data",
"oauthAudience": "standard-user",
},
{
"id": "google-analytics-admin",
"oauthAudience": "standard-user",
},
{
"id": "google-classroom",
"oauthAudience": "advanced-user",
Expand Down
66 changes: 65 additions & 1 deletion packages/plugins/openapi/src/providers/google/presets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { Effect } from "effect";
import { compileOpenApiSpec } from "@executor-js/plugin-openapi";

import { convertGoogleDiscoveryBundleToOpenApi } from "./discovery";
import { googleCatalog, googleOpenApiPresets, googleStandardUserOAuthPresets } from "./presets";
import {
googleCatalog,
googleCatalogOAuthScopesForPreset,
googleOpenApiPresets,
googleStandardUserOAuthPresets,
} from "./presets";

const googleHealthCheckDiscoveryFixtures = {
"google-calendar": {
Expand Down Expand Up @@ -170,6 +175,46 @@ const googleHealthCheckDiscoveryFixtures = {
},
},
},
"google-analytics-admin": {
url: "https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta",
document: {
name: "analyticsadmin",
version: "v1beta",
title: "Google Analytics Admin API",
rootUrl: "https://analyticsadmin.googleapis.com/",
servicePath: "",
resources: {
accountSummaries: {
methods: {
list: {
id: "analyticsadmin.accountSummaries.list",
path: "v1beta/accountSummaries",
httpMethod: "GET",
response: { $ref: "GoogleAnalyticsAdminV1betaListAccountSummariesResponse" },
},
},
},
},
schemas: {
GoogleAnalyticsAdminV1betaListAccountSummariesResponse: {
type: "object",
properties: {
accountSummaries: {
type: "array",
items: { $ref: "GoogleAnalyticsAdminV1betaAccountSummary" },
},
},
},
GoogleAnalyticsAdminV1betaAccountSummary: {
type: "object",
properties: {
account: { type: "string" },
displayName: { type: "string" },
},
},
},
},
},
} as const;

const FROZEN_GOOGLE_SLUGS = [
Expand All @@ -188,6 +233,8 @@ const FROZEN_GOOGLE_SLUGS = [
"google_keep",
"google_youtube_data",
"google_search_console",
"google_analytics_data",
"google_analytics_admin",
"google_classroom",
"google_admin_directory",
"google_admin_reports",
Expand All @@ -204,6 +251,8 @@ it("keeps Select all limited to Google services that can use normal user OAuth",
expect(standardIds).toContain("google-tasks");
expect(standardIds).toContain("google-people");
expect(standardIds).toContain("google-search-console");
expect(standardIds).toContain("google-analytics-data");
expect(standardIds).toContain("google-analytics-admin");

expect(standardIds).not.toContain("google-youtube-data");
expect(standardIds).not.toContain("google-cloud-resource-manager");
Expand All @@ -213,6 +262,20 @@ it("keeps Select all limited to Google services that can use normal user OAuth",
expect(standardIds).not.toContain("google-admin-reports");
});

it("uses read-only OAuth for both Google Analytics APIs", () => {
const readOnlyScope = "https://www.googleapis.com/auth/analytics.readonly";
const writeScopes = [
"https://www.googleapis.com/auth/analytics",
"https://www.googleapis.com/auth/analytics.edit",
];

for (const presetId of ["google-analytics-data", "google-analytics-admin"]) {
const scopes = googleCatalogOAuthScopesForPreset(presetId);
expect(scopes).toContain(readOnlyScope);
for (const writeScope of writeScopes) expect(scopes).not.toContain(writeScope);
}
});

it("classifies every Google service for bundle OAuth UX", () => {
expect(
googleOpenApiPresets.map((preset) => ({
Expand Down Expand Up @@ -273,6 +336,7 @@ it("omits Google health checks when the service spec has no stable cheap read",
"google-slides",
"google-forms",
"google-photos-picker",
"google-analytics-data",
"google-admin-reports",
];

Expand Down
17 changes: 17 additions & 0 deletions packages/plugins/openapi/src/providers/google/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ export const googleOpenApiPresets: readonly GoogleOpenApiPreset[] = [
url: gd("searchconsole", "v1"),
oauthAudience: "standard-user",
},
{
id: "google-analytics-data",
name: "Google Analytics Data",
summary: "Reports, realtime metrics, funnels, and audience exports for GA4 properties.",
url: "https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta",
oauthAudience: "standard-user",
},
{
id: "google-analytics-admin",
name: "Google Analytics Admin",
summary: "Accounts, properties, data streams, key events, and analytics configuration.",
url: "https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta",
oauthAudience: "standard-user",
},
{
id: "google-classroom",
name: "Google Classroom",
Expand Down Expand Up @@ -241,6 +255,8 @@ export const googleOAuthConsentScopes: Readonly<Record<string, readonly string[]
"google-keep": ["https://www.googleapis.com/auth/keep"],
"google-youtube-data": ["https://www.googleapis.com/auth/youtube"],
"google-search-console": ["https://www.googleapis.com/auth/webmasters"],
"google-analytics-data": ["https://www.googleapis.com/auth/analytics.readonly"],
"google-analytics-admin": ["https://www.googleapis.com/auth/analytics.readonly"],
"google-classroom": ["https://www.googleapis.com/auth/classroom.courses"],
"google-admin-directory": ["https://www.googleapis.com/auth/admin.directory.user"],
"google-admin-reports": ["https://www.googleapis.com/auth/admin.reports.audit.readonly"],
Expand Down Expand Up @@ -281,6 +297,7 @@ const GOOGLE_HEALTH_CHECKS: Readonly<Record<string, HealthCheckSpec>> = {
args: { part: "id", mine: true },
},
"google-search-console": { operation: "webmasters.sites.list" },
"google-analytics-admin": { operation: "analyticsadmin.accountSummaries.list" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Health check uses wrong tool name

When an Analytics Admin connection is checked, conversion strips the analyticsadmin. prefix and generates accountSummaries.list, but this health check searches for analyticsadmin.accountSummaries.list, causing validation to return an unknown “operation not found” result without probing the connection.

Knowledge Base Used: Plugin ecosystem

Fix in Codex Fix in Cursor Cloud Agents Fix in Claude Code Fix in Cursor

"google-classroom": { operation: "classroom.courses.list" },
"google-admin-directory": {
operation: "directory.users.list",
Expand Down
Loading