Skip to content

02 — Code Updates

This document describes every change made in the connector-rewrite/ files (browse on GitHub). Changes are grouped by category: bugs first, then security, then standard compliance, then code quality. Each entry includes what changed, why it matters, and the before/after code.

Bug fixes

BUG-1 — fetchDataFromApi returns the Response object instead of JSON text

File: Code.gs | Severity: Critical

UrlFetchApp.fetch() returns an HTTPResponse object. The original code returned this object directly. The caller then passed it to JSON.parse(), which received the string "[object Object]" and threw a SyntaxError. Every non-cached data fetch failed silently.

Before:

function fetchDataFromApi(intKey, apiKey, startDate, endDate) {
  var url = cc.apiConnectorUrl + startDate + "&to=" + endDate;
  var options = {headers: {'X-Integration-Key' : intKey, 'X-Api-Key' : apiKey}};
  try {
    var response = UrlFetchApp.fetch(url, options);
  } catch(e) { ... }
  return response;  // ← Returns HTTPResponse object, not a string
}

After:

function fetchDataFromApi(intKey, apiKey, startDate, endDate) {
  var url = API_CONNECTOR_URL + startDate + '&to=' + endDate;
  var options = {
    headers: {'X-Integration-Key': intKey, 'X-Api-Key': apiKey},
    muteHttpExceptions: true,
    timeout: 10
  };
  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() !== 200) {
    cc.newUserError()
      .setDebugText('API returned HTTP ' + response.getResponseCode())
      .setText('The connector could not retrieve data. Please try again or contact Adventive support.')
      .throwException();
  }
  return response.getContentText();  // ← Returns the JSON string
}


BUG-2 — Cache key does not include requested fields

File: DataCache.gs / Code.gs | Severity: Medium

The cache key was intKey_startDate_endDate. If a user first loaded a chart with fields [cn, i] and then added the c field, the cached response was returned for the new request. The getColumns function found no c value in the cached rows and returned empty strings for that column — silently, with no error.

Before (DataCache.gs):

DataCache.prototype.buildCacheKey = function(cacheToken, startDate, endDate) {
  return cacheToken + '_' + startDate + '_' + endDate;
};

Before (Code.gs):

var cache = new DataCache(CacheService.getUserCache(), cacheToken, startDate, endDate);

After (DataCache.gs):

DataCache.prototype.buildCacheKey = function(cacheToken, startDate, endDate, fieldKey) {
  return cacheToken + '_' + startDate + '_' + endDate + '_' + (fieldKey || '');
};

After (Code.gs):

var fieldKey = request.fields.map(function(f) { return f.name; }).sort().join(',');
var cache = new DataCache(CacheService.getUserCache(), cacheToken, startDate, endDate, fieldKey);


BUG-3 — Empty string credentials bypass null check

File: Auth.gs | Severity: Medium

validateCredentials checked if (username === null || password === null) but not for empty strings. A user who submitted the credentials form with blank fields would pass the null check and trigger an API call with empty header values, resulting in a confusing error.

Before:

function validateCredentials(username, password) {
  if (username === null || password === null) {
    return false;
  }
  ...
}

After:

function validateCredentials(username, password) {
  if (!username || !password) {
    return false;
  }
  ...
}


Security

SEC-1 — Integration Key logged to Cloud Logging

File: Code.gs | Severity: High

The cacheToken variable holds the Integration Key (a credential). It was included in a console.log statement on every data request. Anyone with access to the Apps Script project's Cloud Logging output could read active Integration Keys.

Before:

console.log("Attempting to fetch data from cache... Query Parameters: CacheToken: " + cacheToken + " Date Range: " + startDate + " - " + endDate);

After:

console.log('Fetching data. Date range: ' + startDate + ' - ' + endDate);


Standard compliance

STD-1 — exceptionLogging deprecated value

File: appscript.json | Severity: Medium

Google renamed Stackdriver to Cloud Logging. The "STACKDRIVER" value continues to work but is deprecated. New projects should use "CLOUD".

Before:

"exceptionLogging": "STACKDRIVER"

After:

"exceptionLogging": "CLOUD"


STD-2 — Product name still references "Data Studio"

File: appscript.json | Severity: Medium

Google rebranded Google Data Studio to Looker Studio in October 2022. The connector's marketplace listing, description, and help text should reflect the current product name to avoid user confusion and to remain consistent with Google's updated developer documentation.

Before (appscript.json):

{
  "dataStudio": {
    "name": "Adventive Ads",
    "description": "Adventive analytics API connector for Data Studio...",
    "shortDescription": "Create custom reports and visualizations with your Adventive analytics metrics."
  }
}

After:

{
  "dataStudio": {
    "name": "Adventive Ads for Looker Studio",
    "description": "Adventive analytics connector for Looker Studio. Access campaign metrics and performance for Adventive-based advertising.",
    "shortDescription": "Create custom Looker Studio reports and visualizations with your Adventive analytics metrics."
  }
}

Note: The manifest key remains "dataStudio" — Google has not updated the Apps Script manifest schema to use "lookerStudio". Do not change the key name.


STD-3 — UrlFetchApp.fetch() without muteHttpExceptions in Auth

File: Auth.gs | Severity: High

Without muteHttpExceptions: true, any non-2xx HTTP response from the credentials endpoint throws a JavaScript exception. A valid 401 Unauthorized response — the expected response for bad credentials — caused an unhandled exception rather than a clean return false. The connector displayed a generic error instead of "invalid credentials."

Before:

try {
  var response = UrlFetchApp.fetch(cc.apiPingEndpoint, options);
} catch(e) {
  return false;
}
if (response.getResponseCode() == 200) {
  return true;
}
return false;

After:

var response = UrlFetchApp.fetch(API_PING_URL, {
  headers: {'X-Integration-Key': username, 'X-Api-Key': password},
  muteHttpExceptions: true,
  timeout: 10
});
return response.getResponseCode() === 200;

The try/catch wrapping the fetch is no longer needed for HTTP errors (they no longer throw). Network-level failures (DNS, timeout) are still caught by the outer try/catch in setCredentials.


STD-4 — UrlFetchApp.fetch() without muteHttpExceptions in data fetch

File: Code.gs | Severity: High

Same issue as STD-3, but in fetchDataFromApi. Any 4xx or 5xx response from the data endpoint threw an unhandled exception. Combined with BUG-1 (wrong return type), the original connector never successfully returned data on a cache miss.

Addressed in the After code shown in BUG-1 above.


Code quality

QA-1 — API URLs assigned as properties on the cc global object

File: Code.gs | Severity: Low

Assigning custom properties to a third-party framework object (cc.apiConnectorUrl, cc.apiPingEndpoint) is fragile. A future SDK update could overwrite these properties without warning. URLs belong in clearly named module-level constants.

Before:

var cc = DataStudioApp.createCommunityConnector();
cc.apiConnectorUrl = 'https://api.adventive.com/v2.0/dataconnector.json?data_connector=true&removeZeros=false&from=';
cc.apiPingEndpoint = 'https://api.adventive.com/v2.0/credentialscheck.json';

After:

var cc = DataStudioApp.createCommunityConnector();
var API_CONNECTOR_URL = 'https://api.adventive.com/v2.0/dataconnector.json?data_connector=true&removeZeros=false&from=';
var API_PING_URL = 'https://api.adventive.com/v2.0/credentialscheck.json';


QA-2 — throwConnectorError re-instantiates the connector

File: Code.gs | Severity: Low

throwConnectorError created a new DataStudioApp.createCommunityConnector() instance instead of using the module-level cc. Wasteful and inconsistent.

Before:

function throwConnectorError(text) {
  DataStudioApp.createCommunityConnector()
    .newUserError()
    .setDebugText(text)
    .setText(text)
    .throwException();
}

After:

function throwConnectorError(text) {
  cc.newUserError()
    .setDebugText(text)
    .setText(text)
    .throwException();
}


QA-3 — DataCache TTL comment is wrong

File: DataCache.gs | Severity: Low

The commented-out line said 43200 with the annotation "6 hours, Google's max". The active value is 1800 (30 minutes). The comment was never updated when the TTL was reduced.

Before:

/** @const - 6 hours, Google's max */
//DataCache.REQUEST_CACHING_TIME = 43200;
DataCache.REQUEST_CACHING_TIME = 1800;

After:

/** @const - 30 minutes */
DataCache.REQUEST_CACHING_TIME = 1800;


QA-4 — No timeout on UrlFetchApp.fetch() calls

File: Code.gs, Auth.gs | Severity: Low

Apps Script defaults to a 30-second timeout for UrlFetchApp. A slow or unresponsive Adventive API would hold the connector open for the full 30 seconds before failing. A 10-second timeout is appropriate for an analytics API; it surfaces the failure faster and keeps Looker Studio responsive.

Added timeout: 10 to all UrlFetchApp.fetch() options objects. See BUG-1 and STD-3 above for the full options objects.


QA-5 — for…in over an array in getColumns

File: Code.gs | Severity: Low | Category: V8 hygiene

V8's enumeration order for for…in over an array follows the ECMAScript spec: integer-index keys are visited in ascending order, then string keys in insertion order. Rhino's order differed for mixed-key arrays. The existing loop happens to be safe (the array has only numeric indices and 1–2 elements), but for…in over arrays is an anti-pattern that a future edit could make unsafe silently. Tightening to a numeric loop removes the risk.

Before:

for (var index in valuePaths) {
  var currentPath = valuePaths[index];
  ...
}

After:

for (var i = 0; i < valuePaths.length; i++) {
  var currentPath = valuePaths[i];
  ...
}

The same treatment is applied to the nested for (var index_keys in keys) block, which iterates Object.keys(row) (also an array).

See §05 — V8 Runtime Migration for the full list of V8 incompatibilities and how this codebase scores against them.


RT-1 — Manifest runtimeVersion set to "V8"

File: appscript.json | Severity: Critical | Category: Runtime

Google retires the legacy Rhino runtime on 2026-01-31. Any Apps Script project that is not on V8 after that date stops executing. The rewrite's appscript.json sets the runtime explicitly so that the manifest is self-describing and doesn't rely on implicit defaults.

Before:

{
  "dataStudio": { ... },
  "exceptionLogging": "STACKDRIVER"
}

(No runtimeVersion field — implicit Rhino on pre-2020 projects.)

After:

{
  "dataStudio": { ... },
  "exceptionLogging": "CLOUD",
  "runtimeVersion": "V8"
}

Full migration procedure — including CSV-audit triage, staging rollout, and rollback — is in §05 — V8 Runtime Migration. The runtime flip and the code-level fixes in this section should be deployed as two separate commits in the order described in §05 so failures can be bisected cleanly.


Pre-deploy verification checklist

After applying the rewrite, verify the following before pushing to the production deployment:

- [ ] appscript.json: exceptionLogging is "CLOUD" (not "STACKDRIVER")
- [ ] appscript.json: runtimeVersion is "V8"
- [ ] appscript.json: name includes "Looker Studio"
- [ ] Code.gs: fetchDataFromApi returns response.getContentText()
- [ ] Code.gs: fetchDataFromApi options include muteHttpExceptions: true
- [ ] Code.gs: No console.log statement includes the Integration Key or cacheToken value
- [ ] Code.gs: DataCache constructor call passes fieldKey as the 5th argument
- [ ] Code.gs: throwConnectorError uses module-level cc, not a new instantiation
- [ ] Code.gs: getColumns uses numeric for-loops, not for…in over arrays
- [ ] Auth.gs: validateCredentials uses !username || !password (not === null check)
- [ ] Auth.gs: UrlFetchApp.fetch options include muteHttpExceptions: true
- [ ] DataCache.gs: buildCacheKey accepts 4 parameters including fieldKey
- [ ] DataCache.gs: REQUEST_CACHING_TIME comment says "30 minutes"
- [ ] CSV audit row for this Script_ID has been reviewed (see §05)
- [ ] Pre-V8 snapshot version created via clasp (§05 step 2)
- [ ] Code-fix commit deployed to staging before the runtime-flip commit (§05 step 3)
- [ ] Test: connector authenticates successfully with valid credentials
- [ ] Test: connector returns an authentication error with invalid credentials
- [ ] Test: connector returns data for a date range with known results
- [ ] Test: a second request for the same date range + fields is served from cache
- [ ] Test: Cloud Logging shows no credential values after a full request cycle

Next: 03 — Deployment & Management