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.

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