Skip to content

Commit 17c3e87

Browse files
committed
feat(google): add Analytics Admin and Data presets
1 parent 8b3a468 commit 17c3e87

10 files changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@executor-js/plugin-openapi": patch
3+
---
4+
5+
Add Google Analytics Data and Admin as first-party Google Discovery presets with read-only OAuth scopes and service-hosted Discovery URLs.

e2e/scenarios/provider-plugins-ui.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ scenario(
3434
await search.fill("gmail");
3535
await dialog.getByRole("link", { name: /^Gmail\b/ }).waitFor();
3636

37+
await search.fill("google analytics");
38+
await dialog.getByRole("link", { name: /^Google Analytics Data\b/ }).waitFor();
39+
await dialog.getByRole("link", { name: /^Google Analytics Admin\b/ }).waitFor();
40+
3741
await search.fill("onedrive");
3842
await dialog.getByRole("link", { name: /^OneDrive Files\b/ }).waitFor();
3943
});

packages/plugins/openapi/src/providers/google/__snapshots__/presets.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ exports[`classifies every Google service for bundle OAuth UX 1`] = `
6666
"id": "google-search-console",
6767
"oauthAudience": "standard-user",
6868
},
69+
{
70+
"id": "google-analytics-data",
71+
"oauthAudience": "standard-user",
72+
},
73+
{
74+
"id": "google-analytics-admin",
75+
"oauthAudience": "standard-user",
76+
},
6977
{
7078
"id": "google-classroom",
7179
"oauthAudience": "advanced-user",

packages/plugins/openapi/src/providers/google/discovery.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ it("accepts only supported HTTPS Google Discovery endpoints", () => {
163163
expect(
164164
normalizeGoogleDiscoveryUrl("https://www.googleapis.com/discovery/v1/apis/keep/v1/rest"),
165165
).toBe("https://keep.googleapis.com/$discovery/rest?version=v1");
166+
expect(
167+
normalizeGoogleDiscoveryUrl(
168+
"https://www.googleapis.com/discovery/v1/apis/analyticsdata/v1beta/rest",
169+
),
170+
).toBe("https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta");
171+
expect(
172+
normalizeGoogleDiscoveryUrl(
173+
"https://www.googleapis.com/discovery/v1/apis/analyticsadmin/v1beta/rest",
174+
),
175+
).toBe("https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta");
166176
expect(
167177
normalizeGoogleDiscoveryUrl("https://chat.googleapis.com/$discovery/rest?version=v1"),
168178
).toBe("https://www.googleapis.com/discovery/v1/apis/chat/v1/rest");
@@ -175,6 +185,16 @@ it("accepts only supported HTTPS Google Discovery endpoints", () => {
175185
expect(
176186
normalizeGoogleDiscoveryUrl("https://keep.googleapis.com/$discovery/rest?version=v1"),
177187
).toBe("https://keep.googleapis.com/$discovery/rest?version=v1");
188+
expect(
189+
normalizeGoogleDiscoveryUrl(
190+
"https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta",
191+
),
192+
).toBe("https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta");
193+
expect(
194+
normalizeGoogleDiscoveryUrl(
195+
"https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta",
196+
),
197+
).toBe("https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta");
178198

179199
expect(isGoogleDiscoveryUrl("https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest")).toBe(
180200
true,

packages/plugins/openapi/src/providers/google/discovery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type GoogleDiscoveryServiceOverride = {
4141
};
4242

4343
const GOOGLE_DISCOVERY_SERVICE_OVERRIDES: Record<string, GoogleDiscoveryServiceOverride> = {
44+
analyticsadmin: { preserveServiceHostedUrl: true },
45+
analyticsdata: { preserveServiceHostedUrl: true },
4446
forms: { preserveServiceHostedUrl: true },
4547
keep: { preserveServiceHostedUrl: true },
4648
[GOOGLE_PHOTOS_PICKER_SERVICE]: {

packages/plugins/openapi/src/providers/google/presets.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { compileOpenApiSpec } from "@executor-js/plugin-openapi";
55
import { convertGoogleDiscoveryBundleToOpenApi } from "./discovery";
66
import {
77
googleCatalog,
8+
googleCatalogOAuthScopesForPreset,
89
googleOAuthConsentScopes,
910
googleOpenApiPresets,
1011
googleStandardUserOAuthPresets,
@@ -175,6 +176,46 @@ const googleHealthCheckDiscoveryFixtures = {
175176
},
176177
},
177178
},
179+
"google-analytics-admin": {
180+
url: "https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta",
181+
document: {
182+
name: "analyticsadmin",
183+
version: "v1beta",
184+
title: "Google Analytics Admin API",
185+
rootUrl: "https://analyticsadmin.googleapis.com/",
186+
servicePath: "",
187+
resources: {
188+
accountSummaries: {
189+
methods: {
190+
list: {
191+
id: "analyticsadmin.accountSummaries.list",
192+
path: "v1beta/accountSummaries",
193+
httpMethod: "GET",
194+
response: { $ref: "GoogleAnalyticsAdminV1betaListAccountSummariesResponse" },
195+
},
196+
},
197+
},
198+
},
199+
schemas: {
200+
GoogleAnalyticsAdminV1betaListAccountSummariesResponse: {
201+
type: "object",
202+
properties: {
203+
accountSummaries: {
204+
type: "array",
205+
items: { $ref: "GoogleAnalyticsAdminV1betaAccountSummary" },
206+
},
207+
},
208+
},
209+
GoogleAnalyticsAdminV1betaAccountSummary: {
210+
type: "object",
211+
properties: {
212+
account: { type: "string" },
213+
displayName: { type: "string" },
214+
},
215+
},
216+
},
217+
},
218+
},
178219
} as const;
179220

180221
const FROZEN_GOOGLE_SLUGS = [
@@ -193,6 +234,8 @@ const FROZEN_GOOGLE_SLUGS = [
193234
"google_chat",
194235
"google_youtube_data",
195236
"google_search_console",
237+
"google_analytics_data",
238+
"google_analytics_admin",
196239
"google_classroom",
197240
"google_admin_directory",
198241
"google_admin_reports",
@@ -210,6 +253,8 @@ it("keeps Select all limited to Google services that can use normal user OAuth",
210253
expect(standardIds).toContain("google-tasks");
211254
expect(standardIds).toContain("google-people");
212255
expect(standardIds).toContain("google-search-console");
256+
expect(standardIds).toContain("google-analytics-data");
257+
expect(standardIds).toContain("google-analytics-admin");
213258

214259
expect(standardIds).not.toContain("google-youtube-data");
215260
expect(standardIds).not.toContain("google-cloud-resource-manager");
@@ -304,6 +349,20 @@ it("does not publish the domain-wide-delegation-only Keep preset", () => {
304349
expect(googleCatalog.some((preset) => preset.id === "google-keep")).toBe(false);
305350
});
306351

352+
it("uses read-only OAuth for both Google Analytics APIs", () => {
353+
const readOnlyScope = "https://www.googleapis.com/auth/analytics.readonly";
354+
const writeScopes = [
355+
"https://www.googleapis.com/auth/analytics",
356+
"https://www.googleapis.com/auth/analytics.edit",
357+
];
358+
359+
for (const presetId of ["google-analytics-data", "google-analytics-admin"]) {
360+
const scopes = googleCatalogOAuthScopesForPreset(presetId);
361+
expect(scopes).toContain(readOnlyScope);
362+
for (const writeScope of writeScopes) expect(scopes).not.toContain(writeScope);
363+
}
364+
});
365+
307366
it("classifies every Google service for bundle OAuth UX", () => {
308367
expect(
309368
googleOpenApiPresets.map((preset) => ({
@@ -369,6 +428,7 @@ it("omits Google health checks when the service spec has no stable cheap read",
369428
"google-slides",
370429
"google-forms",
371430
"google-photos-picker",
431+
"google-analytics-data",
372432
];
373433

374434
for (const presetId of omitted) {

packages/plugins/openapi/src/providers/google/presets.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@ export const googleOpenApiPresets: readonly GoogleOpenApiPreset[] = [
184184
icon: GOOGLE_G,
185185
oauthAudience: "standard-user",
186186
},
187+
{
188+
id: "google-analytics-data",
189+
name: "Google Analytics Data",
190+
summary: "Reports, realtime metrics, funnels, and audience exports for GA4 properties.",
191+
url: "https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta",
192+
oauthAudience: "standard-user",
193+
},
194+
{
195+
id: "google-analytics-admin",
196+
name: "Google Analytics Admin",
197+
summary: "Accounts, properties, data streams, key events, and analytics configuration.",
198+
url: "https://analyticsadmin.googleapis.com/$discovery/rest?version=v1beta",
199+
oauthAudience: "standard-user",
200+
},
187201
{
188202
id: "google-classroom",
189203
name: "Google Classroom",
@@ -297,6 +311,7 @@ const GOOGLE_HEALTH_CHECKS: Readonly<Record<string, HealthCheckSpec>> = {
297311
args: { part: "id", mine: true },
298312
},
299313
"google-search-console": { operation: "webmasters.sites.list" },
314+
"google-analytics-admin": { operation: "analyticsadmin.accountSummaries.list" },
300315
"google-classroom": { operation: "classroom.courses.list" },
301316
"google-admin-directory": {
302317
operation: "directory.users.list",

packages/plugins/openapi/src/providers/google/service-policy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export const googleOAuthConsentScopes: Readonly<Record<string, readonly string[]
6666
"google-keep": [auth("keep")],
6767
"google-youtube-data": [auth("youtube.force-ssl"), auth("youtube.channel-memberships.creator")],
6868
"google-search-console": [auth("webmasters")],
69+
"google-analytics-data": [auth("analytics.readonly")],
70+
"google-analytics-admin": [auth("analytics.readonly")],
6971
"google-classroom": [
7072
auth("classroom.announcements"),
7173
auth("classroom.courses"),
@@ -223,6 +225,8 @@ const GOOGLE_DISCOVERY_POLICIES: Readonly<Record<string, GoogleDiscoveryServiceP
223225
]),
224226
}),
225227
"searchconsole/v1": policy("google-search-console"),
228+
"analyticsdata/v1beta": policy("google-analytics-data"),
229+
"analyticsadmin/v1beta": policy("google-analytics-admin"),
226230
"classroom/v1": policy("google-classroom", {
227231
blockedMethodIds: new Set(["classroom.courses.teachers.create"]),
228232
blockedMethodPrefixes: ["classroom.courses.studentGroups.", "classroom.courses.posts."],

packages/plugins/provider-service-split/src/planner.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ const googleCatalogMethodPrefixFixtures: ReadonlyMap<string, readonly string[]>
154154
["google-chat", ["chat.spaces.list"]],
155155
["google-youtube-data", ["youtube.channels.list"]],
156156
["google-search-console", ["searchconsole.sites.list", "webmasters.sites.list"]],
157+
["google-analytics-data", ["analyticsdata.properties.runReport"]],
158+
["google-analytics-admin", ["analyticsadmin.accountSummaries.list"]],
157159
["google-classroom", ["classroom.courses.list"]],
158160
[
159161
"google-admin-directory",

packages/plugins/provider-service-split/src/planner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ const GOOGLE_TOOL_PREFIX_TO_PRESET_ID: ReadonlyMap<string, string> = new Map([
280280
["youtube", "google-youtube-data"],
281281
["searchconsole", "google-search-console"],
282282
["webmasters", "google-search-console"],
283+
["analyticsdata", "google-analytics-data"],
284+
["analyticsadmin", "google-analytics-admin"],
283285
["classroom", "google-classroom"],
284286
["directory", "google-admin-directory"],
285287
["reports", "google-admin-reports"],

0 commit comments

Comments
 (0)