Skip to content

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 to 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: Code Updates