Skip to contents

The doPost() handler to paste into the Sheet's bound Apps Script project. It appends a batch of rows in one setValues() call (the client sends {rows: [...]}), which is what keeps the write cost — and the Apps Script execution quota — flat regardless of interaction rate.

Usage

cc_apps_script()

Value

character scalar of JavaScript source

Details

Column order is taken from cc_log_header(), so the Sheet, the script, and the client payload cannot drift.

Examples

cat(cc_apps_script())
#> // Code.gs — CalCOFI usage log (bound to the log Sheet).
#> // Generated by calcofi4r::cc_apps_script() — do not hand-edit.
#> // Deploy: Deploy > New deployment > type "Web app",
#> //         execute as "Me", who has access "Anyone". Copy the /exec URL into
#> //         the CALCOFI_LOG_URL environment variable for the Shiny apps.
#> //
#> // The client (calcofi4r::cc_ga_js) posts {rows:[{...}, ...]} as text/plain so
#> // the request stays CORS-simple (this endpoint does not answer OPTIONS).
#> 
#> var COLS = ["timestamp","ip","session","event","params","n_rows","ms","status","error","app_version","app","client_id","session_id","page","referrer","user_agent"];
#> 
#> // Health check. Without this, opening the /exec URL in a browser returns
#> // "Script function not found: doGet", which looks like a broken or unauthorized
#> // deployment and sends you hunting through deployment settings. It is not — the
#> // client only ever POSTs. A GET now answers {ok:true,...} so the endpoint can be
#> // verified at a glance. Reports the row count so you can confirm writes land.
#> function doGet(e) {
#>   try {
#>     var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
#>     return ContentService
#>       .createTextOutput(JSON.stringify({ ok: true, endpoint: "calcofi-usage-log", rows: sh.getLastRow() - 1 }))
#>       .setMimeType(ContentService.MimeType.JSON);
#>   } catch (err) {
#>     return ContentService
#>       .createTextOutput(JSON.stringify({ ok: false, error: String(err) }))
#>       .setMimeType(ContentService.MimeType.JSON);
#>   }
#> }
#> 
#> function doPost(e) {
#>   try {
#>     var sh   = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
#>     var body = JSON.parse(e.postData.contents);
#>     var rows = body.rows || [body];
#>     if (!rows.length) return _ok(0);
#> 
#>     // one setValues() for the whole batch — far cheaper than appendRow() per event
#>     var values = rows.map(function (r) {
#>       return COLS.map(function (c) {
#>         if (c === "timestamp")
#>           // a real Date, matching the rows written before the client-side
#>           // rewrite — an ISO string would land as unsortable text.
#>           return r.timestamp ? new Date(r.timestamp) : new Date();
#>         return r[c] === undefined ? "" : r[c];
#>       });
#>     });
#>     sh.getRange(sh.getLastRow() + 1, 1, values.length, COLS.length).setValues(values);
#>     return _ok(values.length);
#>   } catch (err) {
#>     return ContentService
#>       .createTextOutput(JSON.stringify({ ok: false, error: String(err) }))
#>       .setMimeType(ContentService.MimeType.JSON);
#>   }
#> }
#> 
#> function _ok(n) {
#>   return ContentService
#>     .createTextOutput(JSON.stringify({ ok: true, n: n }))
#>     .setMimeType(ContentService.MimeType.JSON);
#> }