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.
Module inventory¶
| File | Purpose | Lines |
|---|---|---|
appscript.json |
Connector manifest — metadata, auth type, OAuth scopes, exception logging, runtime version | 35 |
Code.gs |
Main connector logic — config, schema, data fetching, cache coordination, field mapping | 393 |
Auth.gs |
Authentication module — getAuthType, setCredentials, isAuthValid, resetAuth, credential validation |
94 |
DataCache.gs |
Chunked cache utility — splits responses > 100 KB across multiple CacheService keys |
86 |
Total: 608 lines across 4 files. Lightweight and appropriately scoped for a read-only analytics connector.
Runtime¶
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.
Architecture¶
┌──────────────────────────────────────────────┐
│ 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) │
└──────────────────────────────────────────────┘
Data flow¶
Authentication setup (one-time per user)¶
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)¶
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
API contract¶
Credential validation endpoint¶
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
Data endpoint¶
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
Field schema (28 fields)¶
| ID | Display name | Type | Group | Notes |
|---|---|---|---|---|
an |
Advertiser | DIMENSION / TEXT | Advertiser | |
cn |
Campaign | DIMENSION / TEXT | Campaign | Default dimension |
sn |
Site | DIMENSION / TEXT | Campaign | |
pn |
Placement | DIMENSION / TEXT | Campaign | |
adn |
Ad | DIMENSION / TEXT | Campaign | |
ymd |
Date | DIMENSION / YEAR_MONTH_DAY | — | |
i |
Impressions | METRIC / NUMBER | Analytics | Default metric |
iu |
Unique Impressions | METRIC / NUMBER | Analytics | |
c |
Clicks | METRIC / NUMBER | Analytics | |
cu |
Unique Clicks | METRIC / NUMBER | Analytics | |
ctr |
CTR % | METRIC / PERCENT | Analytics | Formula: SUM($c) / SUM($i) — calculated in Looker Studio |
e |
Engagements | METRIC / NUMBER | Analytics | |
eu |
Unique Engagements | METRIC / NUMBER | Analytics | |
er |
ER % | METRIC / PERCENT | Analytics | Formula: SUM($eu) / SUM($i) — calculated in Looker Studio |
v0 |
Video Plays | METRIC / NUMBER | Analytics | |
v1 |
Video 25% | METRIC / NUMBER | Analytics | |
v2 |
Video 50% | METRIC / NUMBER | Analytics | |
v3 |
Video 75% | METRIC / NUMBER | Analytics | |
v4 |
Video Complete | METRIC / NUMBER | Analytics | |
vcr |
VCR % | METRIC / PERCENT | Analytics | Formula: 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).
Credential storage¶
| Property key | Value | Scope |
|---|---|---|
dscc.username |
Adventive Integration Key | User properties (per-user, isolated) |
dscc.password |
Adventive API Key | User 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.
Issues register — April 2026 audit¶
| # | Issue | Severity | Location | Category |
|---|---|---|---|---|
| 1 | fetchDataFromApi returns the HTTPResponse object; caller passes it directly to JSON.parse(). Every non-cached data fetch crashes with a SyntaxError. |
Critical | Code.gs:312 |
Bug |
| 2 | Integration Key (cacheToken) is written to Cloud Logging on every data request. |
High | Code.gs:253 |
Security |
| 3 | UrlFetchApp.fetch() in validateCredentials has no muteHttpExceptions. A 401 from the API throws an uncaught exception instead of returning false. |
High | Auth.gs:50 |
Standard compliance |
| 4 | UrlFetchApp.fetch() in fetchDataFromApi has no muteHttpExceptions. Any non-2xx response surfaces as an unhandled exception. |
High | Code.gs:302 |
Standard compliance |
| 5 | exceptionLogging is "STACKDRIVER" — deprecated name for Cloud Logging. |
Medium | appscript.json |
Standard compliance |
| 6 | All user-visible text says "Data Studio". Google rebranded to "Looker Studio" in October 2022. | Medium | appscript.json, Code.gs |
Standard compliance |
| 7 | Cache 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. | Medium | DataCache.gs:30 |
Bug |
| 8 | validateCredentials null-checks credentials but not empty string. An empty Integration Key passes validation and makes an invalid API request. |
Medium | Auth.gs:38 |
Bug |
| 9 | API endpoint URLs are assigned as properties on the cc (DataStudioApp) object — mutating a third-party global. |
Low | Code.gs:23-24 |
Code quality |
| 10 | No timeout on UrlFetchApp.fetch() calls. Connector can hang for up to 30 seconds on a slow API response. |
Low | Code.gs:302, Auth.gs:50 |
Code quality |
| 11 | throwConnectorError re-instantiates DataStudioApp.createCommunityConnector() instead of using the module-level cc. |
Low | Code.gs:385 |
Code quality |
| 12 | DataCache.REQUEST_CACHING_TIME comment says "6 hours, Google's max" but the value is 1800 seconds (30 minutes). |
Low | DataCache.gs:18-20 |
Code quality |
| 13 | getColumns 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. |
Low | Code.gs:371, 384 |
V8 hygiene |
| 14 | appscript.json lacks an explicit runtimeVersion: "V8". If the project was created before 2020 it may still be on Rhino — which retires 2026-01-31. |
Critical | appscript.json |
Runtime |
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