Data Studio Connector getData() not running - google-apps-script

I can't seem to get the getData() function to run on this connector I'm building. Data studio displays my Schema properly, however when I go to 'explore' the data, an error is thrown. Looking in the project executions, the 'getData' function never runs at all.
Data Studio has encountered a system error.
Sorry, we encountered an error and were unable to complete your request.
There's no debug errors shown, and I'm not sure how to continue debugging this.
Here is my code...
var cc = DataStudioApp.createCommunityConnector();
function isAdminUser(){
return true
}
function responseToRows(requestedFields, response){
return response.map(function(item) {
var row = [];
requestedFields.asArray().forEach(function(field){
var id = field.getId()
row.push(item[id])
});
console.log(row);
return { values: row };
});
}
function getAuthType() {
var response = { type: 'NONE' };
return response;
}
function getConfig(){
var json = UrlFetchApp.fetch("<api-url>");
var data = JSON.parse(json);
var config = cc.getConfig();
var tables = data.TableNames
var configElement = config
.newSelectSingle()
.setId('tables')
.setName("Choose your data source")
.setHelpText('Choose your data source');
for(i=0;i<tables.length;i++){
configElement
.addOption(config.newOptionBuilder().setLabel(tables[i]).setValue(tables[i]))
}
return config.build();
}
function getSchema(request){
var fields = cc.getFields();
var types = cc.FieldType;
var table = request.configParams.tables;
var data = UrlFetchApp.fetch("<api-url>"+"?name="+table);
var itemArray = JSON.parse(data);
var singleRow = itemArray["Items"][0];
var keys = Object.keys(singleRow)
for(i=0;i<keys.length;i++){
var nestedKeys = Object.keys(singleRow[keys[i]])
var propName = keys[i];
var dataType = nestedKeys[0]
if(dataType == "S"){
fields.newDimension()
.setId(propName)
.setName(propName)
.setType(types.TEXT)
}else if (dataType == "N"){
fields.newMetric()
.setId(propName)
.setName(propName)
.setType(types.NUMBER)
}
}
console.log(fields.build());
console.log('get schema')
return { schema: fields.build() };
}
function getData(request){
var fields = cc.getFields();
console.log(fields);
console.log('getdata running');
// TODO: Create Schema for requested field
var table = request.configParams.tables;
var requestedFieldIds = request.fields.map(function(field) {
return field.name
});
var requestedFields = fields.forIds(requestedFieldIds);
// TODO: Fetch and Parse data from API
var response = UrlFetchApp.fetch("<api-url>"+"?name="+table);
var parsedResponse = JSON.parse(response)
// TODO: Transform parsed data and filter for requested fields
var rows = responseToRows(requestedFields, parsedResponse)
return {
schema: requestedFields.build(),
rows: rows
}
}

To see debug traces, you could simply log it with console.log() and take a look at your logs in the Google Apps Scripts dashboard :
https://script.google.com/home/executions

I don't know if this is related to your problem, but in my case I was trying to use URL Parameters and getData(request) wouldn't run no matter what values I input - it ended up being that I had to create a production deployment and Publish > Deploy from Manifest and then create an actual published version (not just FROM HEAD).

Related

Error handling - retry urlfetch on error until success

I've looked at all the relevant questions here (such as this), but still cannot make sense of this VERY simple task.
So, trying to verify numbers using the NumVerify API. We're still on the free license on APILAYER so we're getting the following error from time to time
Request failed for https://apilayer.net returned code 500
I'd like to add a loop so that the script will try again until a proper response is received.
Here is a snippet based on several answers here:
function numverifylookup(mobilephone) {
console.log("input number: ",mobilephone);
var lookupUrl = "https://apilayer.net/api/validate?access_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&number="+mobilephone+"&country_code=IL";
try {
var response = UrlFetchApp.fetch(lookupUrl);
if (response) {//Check for truthy value
var json = response.getContentText();
} else {
Utilities.sleep(2000);
continue;//If "get" returned a falsy value then continue
}
} catch(e) {
continue;//If error continue looping
}
var data = JSON.parse(response);
Sadly, still not working due to the following error:
Continue must be inside loop. (line 10
Any thoughts?
I think it's actually better to solve this using muteHTTPexepctions but couldn't quite make it work.
Thanks!
I think I got this to work as below:
function numverify(mobilephone);
console.log("input number: ",mobilephone);
var lookupUrl = "https://apilayer.net/api/validate?access_key=XXXXXXXXXXXX&number="+mobilephone+"&country_code=IL";
var i = 0;
var trycount = 1;
var errorcodes = "";
while (i != 1) {
var response = UrlFetchApp.fetch(lookupUrl, {muteHttpExceptions: true });
var responsecode = response.getResponseCode();
var errorcodes = errorcodes + "," + responsecode;
if (responsecode = 200) {//Check for truthy value
var json = response.getContentText();
var i = 1
} else {
var trycount = trycount + 1;
Utilities.sleep(2000);
}
}
var data = JSON.parse(response);
var valid = data.valid;
var localnum = data.local_format;
var linetype = data.line_type;
console.log(data," ",valid," ",localnum," ",linetype," number of tries= ",trycount," responsecodes= ", errorcodes);
var answer = [valid,localnum,linetype];
return answer;
}
I'll circle back in case it still doesn't work.
Thanks for helping!
You cannot use continue to achieve what you want, instead you can / need to call the function again:
function numverifylookup(mobilephone) {
console.log("input number: ", mobilephone);
var lookupUrl = "https://apilayer.net/api/validate?access_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&number=" + mobilephone + "&country_code=IL";
try {
var response = UrlFetchApp.fetch(lookupUrl);
if (response) {//Check for truthy value
var json = response.getContentText();
} else {
Utilities.sleep(2000);
numverifylookup(mobilephone);
}
} catch (e) {
Utilities.sleep(2000);
numverifylookup(mobilephone);//If error rerun the function
}
var data = JSON.parse(response);
}
As you can draw from the documentation the statement continue can only be used inside of loops, like e.g. the for loop.

Data Studio Community Connector for Bigquery - advanced-services

I am customizing an advanced service community connector. In this case to keep it simple I omit the parameters and do a query.
I follow the following example:
Advanced Services de Data Studio
I checked the getData () method for better documentation:
A JavaScript object representing the BigQuery query configuration.
I checked the Data Studio advanced service example repository for the most complete example:
getData(request)
Manifest : "useQueryConfig": true
The code is as follows (a simple query):
<code>var sqlString = ' SELECT name FROM `xxxx-101010.xxxx.xxxxxx` ';
function getAuthType() {
var cc = DataStudioApp.createCommunityConnector();
return cc.newAuthTypeResponse()
.setAuthType(cc.AuthType.NONE)
.build();
}
function getConfig(request) {
var cc = DataStudioApp.createCommunityConnector();
var config = cc.getConfig();
return config.build();
}
function getFields(request) {
var cc = DataStudioApp.createCommunityConnector();
var fields = cc.getFields();
var types = cc.FieldType;
fields.newDimension()
.setId('newDimensionName')
.setName('Package')
.setType(types.TEXT);
return fields;
}
function getSchema(request) {
return {schema: getFields().build()};
}
function getData(request) {
var authToken = ScriptApp.getOAuthToken();
var bqConfig = DataStudioApp.createCommunityConnector().newBigQueryConfig()
.setAccessToken(authToken)
.setUseStandardSql(true)
.setQuery(sqlString)
.build();
return bqConfig
}
</code>
The query I have tried with a table created by me (Permission: BigQuery data owner) and a public table (Permissions by default).
Error (Data Studio)

Flutter Exception Error Handling Google Apps Script generated json

Developing my first Flutter mobile app, a code snippet to fetch a json from:
'https://my-json-server.typicode.com/typicode/demo/posts'
...successfully responds, decodes, parses, etc. Then when i test with doc uploaded to git as:
'https://raw.githubusercontent.com/rays-github/theirmenu/master/db.json'
...this also works. But when I try to use my own data (Google Web Apps Script publishing a Google Sheets spreadsheet as json):
'https://script.googleusercontent.com/macros/echo?user_content_key=_DZABYr6b6k274bCyLNtzSBd1jtYF_WpuFDYAtNQT-uE6uj0teMefPEiNDxNisIH0ew63RSj757Xh5smCcvouuLLk_VcYyB8m5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnPKmEGJr49ifP_3P8Fcnrtzcwn0zyFgFMfS_we8kf_vIvupeaUN7ec2K60MRzRqUBQ&lib=MNDmyszRDOPMr7WJ3Tg4jKCcl7uh4ZtSK'
...I get errors:
Exception has occurred.
FormatException (FormatException: Unexpected character (at line 2, character 1)
<!DOCTYPE html>
^
)
Here is my Flutter snippet:
import 'dart:convert';
import 'package:theirmenu001pt00/tm_menuitem_model.dart';
import 'package:http/http.dart';
class HttpService {
// final String postsUrl = "https://my-json-server.typicode.com/typicode/demo/posts";
// final String postsUrl = "https://raw.githubusercontent.com/rays-github/theirmenu/master/db.json";
final String postsUrl = "https://script.googleusercontent.com/macros/echo?user_content_key=7zmRRkd__iPae6VZ9oq5TTNjfEm3QQV9EYBvQN-awvPS4-HNw2C4wbUSC8ud0J9rfFuxXvwhWPMjiJj5GUVQvGHDvinAYraCm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnPKmEGJr49ifP_3P8Fcnrtzcwn0zyFgFMfS_we8kf_vIvupeaUN7ec2K60MRzRqUBQ&lib=MNDmyszRDOPMr7WJ3Tg4jKCcl7uh4ZtSK";
Future<List<Post>> getMenuItems() async {
Response res = await get(postsUrl);
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<Post> posts =
body.map((dynamic item) => Post.fromJson(item)).toList();
return posts;
} else {
throw "Can't get posts.";
}
}
}
Here is my Google Web App Script:
function doGet(e){
// Sheet url
var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1jsBS-RBNRxYU66WFkJHvrzHLGmNqxBzzQfaHJO6i6UY/edit#gid=446843772");
// Sheet Name
var sheet = ss.getSheetByName("Users");
return getUsers(sheet);
}
function getUsers(sheet){
var jo = {};
var dataArray = [];
// collecting data from 2nd Row , 1st column to last row and last column
var rows = sheet.getRange(2,1,sheet.getLastRow()-1, sheet.getLastColumn()).getValues();
for(var i = 0, l= rows.length; i<l ; i++){
var dataRow = rows[i];
var record = {};
record['userId'] = dataRow[0];
record['id'] = dataRow[1];
record['title'] = dataRow[2];
record['body'] = dataRow[3];
dataArray.push(record);
}
jo = dataArray;
var result = JSON.stringify(jo);
return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.JSON);
}
SCREENSHOT - MICROSOFT VISUAL STUDIO CODE RUN:
SCREENSHOT - UNAUTHENTICATED REQUEST VIA BROWSER:
Please, advise. Any help is greatly appreciated.
thanks!

How to pull data from multiple Mailchimp endpoints?

The code below pulls data from the Mailchimp API Reports endpoint and adding it to Sheets.
I would like to add some more data from other endpoints (like fields from the "List/Audience" endpoint: member_count, total_contacts i.e.) but don't have a slick solution to this.
What's the best practice/solution here? Can this task be kept in the same function or is a separate function preferable?
I'm new in this area so bear with me :)
function chimpCampaigns() {
var API_KEY = 'X'; // MailChimp API Key
var REPORT_START_DATE = '2018-01-01 15:54:00'; // Report Start Date (ex. when you sent your first MailChimp Newsletter)
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("CampaignData");
var dc = API_KEY.split('-')[1];
var api = 'https://'+ dc +'.api.mailchimp.com/3.0';
var count = 100; // Max rows to return
var campaignList = '/campaigns?&count='+count+'&since_send_time='+REPORT_START_DATE
var options = {"headers": {"authorization": 'apikey '+API_KEY}};
var apiCall = function(endpoint){
apiResponseCampaigns = UrlFetchApp.fetch(api+endpoint,options);
json = JSON.parse(apiResponseCampaigns);
return json
}
var campaigns = apiCall(campaignList);
var total = campaigns.total_items;
var campaignData = campaigns.campaigns;
if (campaignData) {
sheet.clear(); // Clear MailChimp data in Spreadsheet
// Append Column Headers
sheet.appendRow(["Sent Time", "Campaign ID", "Audience", "Campaign Title", "Subject Line", "Emails Sent", "Abuse Reports", "Unsubscribed", "Unsubscribe Rate", "Hard Bounces", "Soft Bounces", "Bounces Total", "Syntax Errors", "Forwards Count", "Forwards Opens", "Opens Total", "Unique Opens", "Open Rate", "Last Open", "Clicks Total", "Unique Clicks","Unique Subscriber Clicks", "Click Rate", "Last Click"]);
}
for (i=0; i< campaignData.length; i++){
var c = campaignData[i];
var cid = c.id;
var title = c.title;
var subject = c.subject;
var send_time = c.send_time;
if (send_time){
apiResponseReports = UrlFetchApp.fetch('https://'+ dc +'.api.mailchimp.com/3.0/reports/'+cid,options);
reports = JSON.parse(apiResponseReports);
reportsSendTime = reports.send_time;
if(reportsSendTime){
var campaign_title = c.settings.title;
var subject_line = c.settings.subject_line;
var emails_sent = reports.emails_sent;
var list_name = reports.list_name;
var fields = reports.fields;
var abuse_reports = reports.abuse_reports;
var unsubscribed = reports.unsubscribed;
var unsubscribe_rate = unsubscribed/emails_sent;
var hard_bounces = reports.bounces.hard_bounces;
var soft_bounces = reports.bounces.soft_bounces;
var bounces = hard_bounces+soft_bounces;
var syntax_errors = reports.bounces.syntax_errors;
var forwards_count = reports.forwards.forwards_count;
var forwards_opens = reports.forwards.forwards_opens;
var opens_total = reports.opens.opens_total;
var unique_opens = reports.opens.unique_opens;
var open_rate = reports.opens.open_rate;
var last_open = reports.opens.last_open;
var clicks_total = reports.clicks.clicks_total;
var unique_clicks = reports.clicks.unique_clicks;
var unique_subscriber_clicks = reports.clicks.unique_subscriber_clicks;
var click_rate = reports.clicks.click_rate;
var last_click = reports.clicks.last_click;
// the report array is how each row will appear on the spreadsheet
var report = [send_time, fields, cid, list_name, campaign_title, emails_sent, subject_line, abuse_reports, unsubscribed, unsubscribe_rate, hard_bounces, soft_bounces, bounces, syntax_errors, forwards_count, forwards_opens, opens_total, unique_opens, open_rate, last_open, clicks_total, unique_clicks, unique_subscriber_clicks, click_rate, last_click];
sheet.appendRow(report);
}
}
}
}
You can call each endpoint in succession using the error-first pattern. More on this here.
If your previous call returns data and doesn't error out, you pass the next function as a callback, etc.
In the example below, I've omitted the logic that builds URL endpoint, query-string, and the 'options' object as these can simply be borrowed from your code.
Basically, you define a function with a callback parameter for each API endpoint.
Whenever you need to call multiple endpoints, you create a 3rd function that calls them in succession, passing each new function call as a parameter to the previous one.
The inner functions will still have access to the outer scope so you can combine data from multiple endpoints after the last call is executed (provided you assign unique names to the returned data - 'campaigns', 'reports', etc)
//function for the 'campaings' endpoint
function getCampaings(options, callback) {
//API call
var response = UrlFetchApp.fetch(campaignsEndpoint, options);
if (res.getStatusCode() == 200) {
var campaigns = JSON.parse(res.getContentText());
callback(false, campaigns);
} else {
callback("Error: Server responded with the status code of " + res.getStatusCode());
}
}
After creating the function for calling the 'reports' endpoint using the same approach, combine calls in the 3rd function.
function getCampaignsAndReports(){
var combinedData = {};
getCampaigns(options, function(err, campaigns){
if (!err && campaigns) {
//Call is successful - proceed to the next call
getReports(options, function(err, reports){
//Call successful
if (!err && reports) {
//Proceed to the next call or combine data from
//multiple endpoints
combinedData.campaigns = campaigns.campaigns;
combinedData.reports = reports.reports;
//write to sheet
//...
} else {
//Error calling reports endpoint
throw err;
}
});
} else {
//Error calling 'campaigns' endpoint. Throw error or write
//another function to show it to the user
throw err;
}
});
}
This may vary depending on how the MailChimp API data is structured so please change the code accordingly. Also, if you need to call the 'reports' endpoint multiple times for each entry in the 'campaings' endpoint, you can change your function to handle multiple request (options) object using UrlFetchApp.fetchAll(request[]). More on this here. Calling this method will return multiple response objects that you can iterate over.

How can I present customer data from spreadsheet into form in app maker for update?

I have struggling to present available data for selected customer from spreadsheet into app maker form incase staff want to change it or update empty fields.
Client side code:
function getDetails() {
var props = app.currentPage.properties;
var page = app.pages.Search;
var Channel = app.datasources.Update.items;
var Customer = page.descendants.Sheets.value;
props.Loading = true;
props.Error = null;
google.script.run
.withFailureHandler(function(error) {
props.Loading = false;
props.Error = JSON.stringify(error);
console.error(error);
})
.withSuccessHandler(function(Channel) {
props.Loading = false;
page.Channel = Channel;
var items = [];
items = getChannels(props.SelectedSheet);
Channel.items.load(); // this line dosen't work and it doesn't load the data into form
if (Channel && Channel.length > 0) {
page.SelectedSheet = Channel[0];
} })
.getDetails(props.SelectedSheet);
}
Server side code:
function getDetails()(customer){
var spreadSheet = SpreadsheetApp.openById("***").getSheetByName('TRACKER');
var data=spreadSheet.getDataRange().getValues();
var channels = [];
var Name = customer;
var string1 = Name;
var array1 = string1.split(";"); // in here I extract row number belong to customer to get data
var destrow = [];
destrow.push(data[array1[0]][0],data[array1[0]][1],data[array1[0]][2],data[array1[0]][3],data[array1[0]][4],data[array1[0]][5]);
channels.push(destrow);
// return channels;
return channels.map(function(Channel){
return Channel;}); // return array of field data to presented in app maker form
}
Thank you for any answer or suggestion.
Cheers
In theory, this code should throw exception, since Channel is array and array doesn't have load method:
function getDetails() {
...
var Channel = app.datasources.Update.items;
...
// your first Channel variable is never used and is overridden with
// Channel callback parameter
.withSuccessHandler(function(Channel) {
// this line does nothing, since all App Maker objects are sealed
page.Channel = Channel;
// TypeError: load is not a function
Channel.items.load();
...
}
It is not clear from you code, what you are trying to do... Try to debug it and look into browser console more often (F12 or Ctrl + Shift + J).
Further reading:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal