Skip to content

01 — Architecture Assessment

This document captures the current state of the Adventive Looker Studio connector as of the April 2026 audit: module structure, data flow, API contract, and a complete register of every issue found. Use this as the baseline before applying any changes from §02.

FilePurposeLines
appscript.jsonConnector manifest — metadata, auth type, OAuth scopes, exception logging, runtime version35
Code.gsMain connector logic — config, schema, data fetching, cache coordination, field mapping393
Auth.gsAuthentication module — getAuthType, setCredentials, isAuthValid, resetAuth, credential validation94
DataCache.gsChunked cache utility — splits responses > 100 KB across multiple CacheService keys86

Total: 608 lines across 4 files. Lightweight and appropriately scoped for a read-only analytics connector.

The connector runs on the Google Apps Script V8 runtime. The manifest explicitly pins "runtimeVersion": "V8". Google retired the legacy Rhino runtime on 2026-01-31; any project without "runtimeVersion": "V8" in its manifest after that date stops executing. See §05 — V8 Runtime Migration for the CSV-audit triage procedure and the code-incompatibility review that scores this codebase against the Rhino → V8 delta.

┌──────────────────────────────────────────────┐
│ Google Looker Studio (browser) │
│ User configures connector, builds reports │
└──────────────────┬───────────────────────────┘
│ Calls connector functions
┌──────────────────▼───────────────────────────┐
│ Google Apps Script Runtime │
│ │
│ Auth.gs Code.gs DataCache.gs │
│ ───────── ──────── ─────────── │
│ getAuthType() getConfig() DataCache() │
│ setCredentials() getSchema() .get() │
│ isAuthValid() getData() .set() │
│ resetAuth() fetchData() │
│ fetchDataFromApi() │
│ getColumns() │
│ │
│ PropertiesService CacheService │
│ (credential store) (response cache) │
└──────────────────┬───────────────────────────┘
│ HTTPS
┌──────────────────▼───────────────────────────┐
│ Adventive Analytics API v2.0 │
│ https://api.adventive.com/v2.0/ │
│ │
│ /credentialscheck.json (auth validation) │
│ /dataconnector.json (metrics data) │
└──────────────────────────────────────────────┘
User enters Integration Key + API Key in Looker Studio UI
→ Auth.setCredentials(request)
→ Auth.validateCredentials(username, password)
→ POST https://api.adventive.com/v2.0/credentialscheck.json
Headers: X-Integration-Key, X-Api-Key
Response 200 → valid
Response 401 → invalid → return INVALID_CREDENTIALS error code
→ PropertiesService.getUserProperties().setProperty('dscc.username', ...)
→ PropertiesService.getUserProperties().setProperty('dscc.password', ...)

Report data fetch (every time a chart renders or refreshes)

Section titled “Report data fetch (every time a chart renders or refreshes)”
Looker Studio calls getData(request)
→ Read credentials from PropertiesService
→ Extract dateRange.startDate, dateRange.endDate from request
→ Build DataCache with key: intKey + '_' + startDate + '_' + endDate
→ fetchDataFromCache(cache)
CacheService.getUserCache().get(chunkKey_0, chunkKey_1, ...)
Hit → JSON.parse(cachedString) → content[]
Miss →
fetchDataFromApi(intKey, apiKey, startDate, endDate)
GET https://api.adventive.com/v2.0/dataconnector.json
?data_connector=true&removeZeros=false&from=STARTDATE&to=ENDDATE
Headers: X-Integration-Key, X-Api-Key
→ putDataInCache(content, cache)
Splits JSON string into 100 KB chunks
CacheService.put(chunkKey_n, chunk, 1800)
→ getColumns(content, requestedFields)
Maps each API row to the ordered list of requested field IDs
Returns [{values: [...]}, ...]
→ Return {schema: [...], rows: [...]} to Looker Studio
GET https://api.adventive.com/v2.0/credentialscheck.json
Headers:
X-Integration-Key: <integration_key>
X-Api-Key: <api_key>
Response:
200 OK → credentials valid
401 Unauthorized → credentials invalid
GET https://api.adventive.com/v2.0/dataconnector.json
?data_connector=true
&removeZeros=false
&from=YYYYMMDD
&to=YYYYMMDD
Headers:
X-Integration-Key: <integration_key>
X-Api-Key: <api_key>
Response: JSON array of row objects, each keyed by field ID
IDDisplay nameTypeGroupNotes
anAdvertiserDIMENSION / TEXTAdvertiser
cnCampaignDIMENSION / TEXTCampaignDefault dimension
snSiteDIMENSION / TEXTCampaign
pnPlacementDIMENSION / TEXTCampaign
adnAdDIMENSION / TEXTCampaign
ymdDateDIMENSION / YEAR_MONTH_DAY
iImpressionsMETRIC / NUMBERAnalyticsDefault metric
iuUnique ImpressionsMETRIC / NUMBERAnalytics
cClicksMETRIC / NUMBERAnalytics
cuUnique ClicksMETRIC / NUMBERAnalytics
ctrCTR %METRIC / PERCENTAnalyticsFormula: SUM($c) / SUM($i) — calculated in Looker Studio
eEngagementsMETRIC / NUMBERAnalytics
euUnique EngagementsMETRIC / NUMBERAnalytics
erER %METRIC / PERCENTAnalyticsFormula: SUM($eu) / SUM($i) — calculated in Looker Studio
v0Video PlaysMETRIC / NUMBERAnalytics
v1Video 25%METRIC / NUMBERAnalytics
v2Video 50%METRIC / NUMBERAnalytics
v3Video 75%METRIC / NUMBERAnalytics
v4Video CompleteMETRIC / NUMBERAnalytics
vcrVCR %METRIC / PERCENTAnalyticsFormula: SUM($v4) / SUM($v0) — calculated in Looker Studio

CTR, ER, and VCR are declared as formula fields in the schema. The API does not return these values — Looker Studio computes them client-side from the raw counts. This is intentional (reduces API response size).

Property keyValueScope
dscc.usernameAdventive Integration KeyUser properties (per-user, isolated)
dscc.passwordAdventive API KeyUser properties (per-user, isolated)

PropertiesService.getUserProperties() provides per-user isolation — one user’s credentials are never visible to another. There is no encryption layer beyond Apps Script’s built-in access controls.

#IssueSeverityLocationCategory
1fetchDataFromApi returns the HTTPResponse object; caller passes it directly to JSON.parse(). Every non-cached data fetch crashes with a SyntaxError.CriticalCode.gs:312Bug
2Integration Key (cacheToken) is written to Cloud Logging on every data request.HighCode.gs:253Security
3UrlFetchApp.fetch() in validateCredentials has no muteHttpExceptions. A 401 from the API throws an uncaught exception instead of returning false.HighAuth.gs:50Standard compliance
4UrlFetchApp.fetch() in fetchDataFromApi has no muteHttpExceptions. Any non-2xx response surfaces as an unhandled exception.HighCode.gs:302Standard compliance
5exceptionLogging is "STACKDRIVER" — deprecated name for Cloud Logging.Mediumappscript.jsonStandard compliance
6All user-visible text says “Data Studio”. Google rebranded to “Looker Studio” in October 2022.Mediumappscript.json, Code.gsStandard compliance
7Cache key does not include the requested field list. A cached response for fields A+B is returned for a subsequent request for fields A+B+C — the extra field silently returns empty strings.MediumDataCache.gs:30Bug
8validateCredentials null-checks credentials but not empty string. An empty Integration Key passes validation and makes an invalid API request.MediumAuth.gs:38Bug
9API endpoint URLs are assigned as properties on the cc (DataStudioApp) object — mutating a third-party global.LowCode.gs:23-24Code quality
10No timeout on UrlFetchApp.fetch() calls. Connector can hang for up to 30 seconds on a slow API response.LowCode.gs:302, Auth.gs:50Code quality
11throwConnectorError re-instantiates DataStudioApp.createCommunityConnector() instead of using the module-level cc.LowCode.gs:385Code quality
12DataCache.REQUEST_CACHING_TIME comment says “6 hours, Google’s max” but the value is 1800 seconds (30 minutes).LowDataCache.gs:18-20Code quality
13getColumns uses for (var i in array) to iterate numeric arrays. Harmless today with 1–2 elements, but for…in over arrays is a V8 foot-gun and blocks any future refactor that adds non-numeric properties.LowCode.gs:371, 384V8 hygiene
14appscript.json lacks an explicit runtimeVersion: "V8". If the project was created before 2020 it may still be on Rhino — which retires 2026-01-31.Criticalappscript.jsonRuntime

All 14 issues are addressed in §02 — Code Updates. The V8-runtime-specific triage and migration procedure are in §05 — V8 Runtime Migration. The rewritten files are in connector-rewrite/ (browse on GitHub).


Next: 02 — Code Updates