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):
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:
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:
After:
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:
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:
After:
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:
(No runtimeVersion field — implicit Rhino on pre-2020 projects.)
After:
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