Data Studio Community Connector for Bigquery - advanced-services - google-apps-script

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)

Related

Google Apps Script Auth function don't show login form

I need to build a community connector. So, I have a get AuthType() function where I specify that the users have to login with their username and password.
I have all the other necessary functions that I have find here : https://developers.google.com/looker-studio/connector/auth#user_pass
When i try to use my connector, it ask me some authorizations relative to google account, but it never show me the login form.
Here is my code :
var cc = DataStudioApp.createCommunityConnector();
function getAuthType() {
resetAuth(); // I tried to call this function to reset credentials; It's define later
return cc.newAuthTypeResponse()
.setAuthType(cc.AuthType.USER_PASS)
.setHelpUrl('https://api.smartxsp.test/auth-help')
.build();
}
/**
* Sets the credentials.
*/
function setCredentials(request) {
var creds = request.userPass;
var username = creds.username;
var password = creds.password;
var validCreds = checkForValidCreds(username, password);
if (!validCreds) {
return {
errorCode: 'INVALID_CREDENTIALS'
};
}
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('dscc.username', username);
userProperties.setProperty('dscc.password', password);
return {
errorCode: 'NONE'
};
}
/**
Call to website to check if credentials are valid
*/
function checkForValidCreds(username, password) {
// This url calls a php function. If credentials are good, it return true, else it return false
var url = 'https://*****?username='+username+'&password='+password;
if(url){
return true;
} else {
return false;
}
return false;
}
function isAuthValid() {
var userProperties = PropertiesService.getUserProperties();
var userName = userProperties.getProperty('dscc.username');
var password = userProperties.getProperty('dscc.password');
return checkForValidCreds(userName, password);
}
function resetAuth() {
var userProperties = PropertiesService.getUserProperties();
userProperties.deleteProperty('dscc.username');
userProperties.deleteProperty('dscc.password');
}
I used this codelab for base and adapted it for my project. https://codelabs.developers.google.com/codelabs/community-connectors#4.
I looked the documentation of google apps script:
https://developers.google.com/looker-studio/connector/auth#user_pass,
https://developers.google.com/looker-studio/connector/reference#required_functions
Finally, I want to be able to give credentials to the applications with a form, in order to get datas linked to the user's profile

How to return google sheet values using doPost using x-www-form-urlencoded?

I try to use google sheets to write and read some data using post requests,
the writing part works, but it never returns any value back.
function doPost(e) { return handleResponse(e); }
function handleResponse(e) {
// Get public lock, one that locks for all invocations
// (https://gsuite-developers.googleblog.com/2011/10/concurrency-and-google-apps-script.html)
var lock = LockService.getPublicLock();
// Allow the write process up to 2 seconds
lock.waitLock(2000);
try {
// Generate a (not very good) UUID for this submission
var submissionID = e.parameter.id || 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
// Open the spreadsheet document and select the right sheet page
var sheetName = e.parameter.sheet_name|| 'Sheet1';
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
var sheet = doc.getSheetByName(sheetName);
//get information out of post request
var action = e.parameter.action || 'save';
var pName = e.parameter.name;
var rowNumber = findRow(pName,sheetName);
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(headRow, 1, 1, sheet.getLastColumn()).getValues()[0];
// check for action is loading
if(action == 'load'){
//check if the name has data
if (rowNumber){
//loads all the give values out of the parameters
var answer = [];
Logger.log('hadders: ' + headers);
for (i in headers) {
if (e.parameter[headers[i].toLowerCase()] !== undefined) {
var val = sheet.getRange(rowNumber, 1, 1,sheet.getLastColumn()).getValues()[0][i];
answer.push(val);
}
}
Logger.log('answer: '+ answer);
// Return result in JSON
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
;
}else{
// return error name wasn't found in sheet.
return ContentService
.createTextOutput("can't find Name")
.setMimeType(ContentService.MimeType.TEXT)
;
}
}
The logger returns all the right values,
but logging the return value from this function ends up in an empty object.
I tried just making my own return object like:
return ContentService
.createTextOutput({body={parameter={answer=JSON.stringify(answer)}}})
.setMimeType(ContentService.MimeType.TEXT)
;
I know that I need to use &= instead of ,: but it still returned nothing.
In your script, how about modifying as follows?
From:
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
To:
return ContentService
.createTextOutput(JSON.stringify({body:{parameter:{answer}}}))
.setMimeType(ContentService.MimeType.JSON)
In the case of createTextOutput({body:{parameter:{answer}}}), the object cannot be directly put. So I thought that it is required to convert it to the string.
Note:
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".

Data Studio Connector getData() not running

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).

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