GAS: BigQuery Upload to Existing table - json

I have over 350,000 RingCentral Call logs I want to upload to BigQuery so I can use SQL queries to pull and digest in reports. They are currently stored as 23 .csv files to keep each under the 10MB limit imposed by the BigQuery API. I want to use Google Apps Script to upload the CSV data so all 350K entries are in one table. Here is the code I have so far:
function uploadCSVtoBigQuery() {
try {
var CSVFolder = "Drive ID to Folder A";
var ProcessedFolder = "Drive ID to Folder B";
var projectId = 'ring-central-call-logs';
var datasetId = 'RingCentral';
var tableId = 'Calls';
//CSVFolder = getDriveFolder(CSVFolder);
var CSVFolder = DriveApp.getFolderById(CSVFolder);
//ProcessedFolder = getDriveFolder(ProcessedFolder);
var ProcessedFolder = DriveApp.getFolderById(ProcessedFolder);
if (CSVFolder && ProcessedFolder) {
Logger.log("Folders Appear Valid");
var data, job, file, files = CSVFolder.getFiles();
while (files.hasNext()) {
file = files.next();
if (file.getMimeType() === "text/csv") {
data = file.getBlob().setContentType('application/octet-stream');
job = {
configuration: {
load: {
destinationTable: {
projectId: projectId,
datasetId: datasetId,
tableId: tableId
},
skipLeadingRows: 1
}
}
};
job = BigQuery.Jobs.insert(job, projectId, data);
file.makeCopy(file.getName(), ProcessedFolder);
file.setTrashed(true);
Logger.log('Job status for %s https://bigquery.cloud.google.com/jobs/%s', file.getName(), projectId);
} else{Logger.log(file.getMimeType()+" Is not a valid file type.");}
}
} else{Logger.log('One of your folders is not valid');}
Logger.log('Finished');
} catch(e) {
Logger.log(e.toString());
}
}
// Return the ID of the Google Drive nested folder
function getDriveFolder(name) {
var results, folders = name.split("\\");
var folder = DriveApp.getRootFolder();
for (var i=0; i<folders.length; i++) {
if (folders[i] === "") continue;
results = folder.getFoldersByName(folders[i]);
if (results.hasNext()) {
folder = results.next();
} else {
folder = folder.createFolder(folders[i]);
}
}
return folder;
}
On running the function I get a JSON response as follows:
GoogleJsonResponseException: Using table ring-central-call-logs:RingCentral.Calls is not allowed for this operation because of its type. Try using a different table that is of type TABLE.
To check my brain, I tried running the REST API in the API Explorer, like this:
POST https://www.googleapis.com/bigquery/v2/projects/ring-central-call-logs/datasets/RingCentral/tables/Calls/insertAll?key={YOUR_API_KEY}
{
"skipInvalidRows": true,
"rows": [
{
"json": {
"User": "Nathaniel",
"CallStartTime": "2018-07-09 15:45:10",
"From": "123456789",
"To": "9876543211",
"DurationSeconds": "15",
"CallDirection": "out",
"CallResult": "Failed",
"QueueName": "Test Queue"
}
}
],
"kind": "bigquery#tableDataInsertAllRequest"
}
And got this similar error:
400
- Show headers -
{
"error": {
"code": 400,
"message": "Cannot add rows to a table of type EXTERNAL.",
"errors": [
{
"message": "Cannot add rows to a table of type EXTERNAL.",
"domain": "global",
"reason": "invalid"
}
],
"status": "INVALID_ARGUMENT"
}
}
Screenshot here for Schema looks to match what I am inputting:
I would suppose I have permissions issue by reading it, but I can run a Query function in GAS just fine. Could I get assistance to figure out why this isn't working? Thank you!

Related

Find all empty folders in a Shared Drive and change color with Apps Script?

I'm new to Apps Script and I'm trying to make a script that finds all the empty folders and change their color. I managed to do it, when I try it in my Drive it works perfectly, but when I try run it in the Shared Drive it gives me this error:
Exception: Request failed for https://www.googleapis.com returned code 404. Truncated server response: {
"error": {
"errors": [
{
"domain": "global",
"reason": "notFound",
"message": "File not found: 166gK63I72FZvqY0XjSE63z4SyQuSgGp... (use muteHttpExceptions option to examine full response)
I searched around for some solutions, the only one being to create a project in Google Cloud Platform. I did so, and linked my script to the project in GCP but it gives the same error.
Basically, I want my script to have access to my Shared Drive. The Shared Drive it's pretty big, it has a lot of folders and files, I don't know if maybe the error is due to too many api calls, or if it's a problem with the permission.
I hope someone can help! Thanks a lot! I leave you here my script:
function findEmptyFolders() {
var parentFolder = DriveApp.getFolderById('Shared Drive ID');
var folders = parentFolder.getFolders();
while (folders.hasNext()) {
var childfolder = folders.next();
recurseFolder(parentFolder, childfolder);
}
}
function recurseFolder(parentFolder, folder) {
var filecount = 0;
var foldercount = 0;
var files = folder.getFiles();
var childfolders = folder.getFolders();
while (childfolders.hasNext()) {
var childfolder = childfolders.next();
recurseFolder(folder, childfolder);
foldercount++;
}
while (files.hasNext()) {
var file = files.next();
filecount++;
}
if (filecount == 0 && foldercount == 0) {
var id = folder.getId();
Logger.log(folderColor.setColorByName(id,'Yellow cab'));
}
}
var folderColor = {};
folderColor.setColorByName = function(id,name){
if(!colorPalette[name]){
throw "Name is not valid, please check name in colorPalette.";
}
this.setColor(id,colorPalette[name]);
return true;
}
folderColor.init = function(){
return this;
}
folderColor.setColor = function(id,hexa){
var url = 'https://www.googleapis.com/drive/v2/files/'+id+'?fields=folderColorRgb';
var param = {
method : "patch",
contentType: 'application/json',
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
payload: JSON.stringify({folderColorRgb:hexa})
};
var html = UrlFetchApp.fetch(url,param).getContentText();
return html;
}
var colorPalette = {
"Chocolate ice cream":"#ac725e",
"Old brick red":"#d06b64",
"Cardinal":"#f83a22",
"Wild straberries":"#fa573c",
"Mars orange":"#ff7537",
"Yellow cab":"#ffad46",
"Spearmint":"#42d692",
"Vern fern":"#16a765",
"Asparagus":"#7bd148",
"Slime green":"#b3dc6c",
"Desert sand":"#fbe983",
"Macaroni":"#fad165",
"Sea foam":"#92e1c0",
"Pool":"#9fe1e7",
"Denim":"#9fc6e7",
"Rainy sky":"#4986e7",
"Blue velvet":"#9a9cff",
"Purple dino":"#b99aff",
"Mouse":"#8f8f8f",
"Mountain grey":"#cabdbf",
"Earthworm":"#cca6ac",
"Bubble gum":"#f691b2",
"Purple rain":"#cd74e6",
"Toy eggplant":"#a47ae2"
};
In your script, how about the following modification?
From:
var url = 'https://www.googleapis.com/drive/v2/files/'+id+'?fields=folderColorRgb';
To:
var url = 'https://www.googleapis.com/drive/v2/files/'+id+'?fields=folderColorRgb&supportsAllDrives=true';
Reference:
Files: patch

How to get YouTube Analytics Report for each video in a branded account using YouTube data API and YouTube Analytics report

I want to be able to get Analytics Report of each video in a branded account.. I am following a tutorial on this website Link: https://mashe.hawksey.info/2017/10/getting-youtube-analytics-with-google-apps-script-when-you-get-authentication-loops/
In the tutorial, i provide a way to get and store Authorized token using "cGoa" Library..
My approach is to first get all the videos in the YoutUbe account and store them into an array, then get Analytics for each video with loop. But i am getting errors while running the code to retreive videos from the channel.
Below is the Code i wanted to use to retrieve Videos.
Note: I am Using YouTube Library Provided in the Tutorial i read from page link above
var goa = cGoa.GoaApp.createGoa('youtube-analytics',
PropertiesService.getUserProperties())
.execute();
YouTube.setTokenService(function(){ return goa.getToken(); } );
//YouTubeAnalytics.setTokenService(function(){ return goa.getToken(); });
var results = YouTube.channelsList('id,snippet,contentDetails,statistics', {mine: true});
//Logger.log(results[0].contentDetails.relatedPlaylists.uploads);
//Logger.log(results[0].id);
var channelId = results[0].id;
for(var i in results) {
var item = results[i];
// Get the playlist ID, which is nested in contentDetails, as described in the
// Channel resource: https://developers.google.com/youtube/v3/docs/channels
var playlistId = item.contentDetails.relatedPlaylists.uploads;
var nextPageToken = '';
var titleArray = [];
var idArray = [];
var reportArray = [];
// This loop retrieves a set of playlist items and checks the nextPageToken in the
// response to determine whether the list contains additional items. It repeats that process
// until it has retrieved all of the items in the list.
while (nextPageToken != null) {
var playlistResponse = YouTube.playlistItemsList('snippet', {
playlistId: playlistId,
maxResults: 25,
pageToken: nextPageToken
});
for (var j = 0; j < playlistResponse.items.length; j++) {
var playlistItem = playlistResponse.items[j];
//Logger.log('[%s] Title: %s',
//playlistItem.snippet.resourceId.videoId,
//playlistItem.snippet.title);
titleArray[j] = playlistItem.snippet.title;
idArray[j] = playlistItem.snippet.resourceId.videoId;
var videoId = playlistItem.snippet.resourceId.videoId;
reportArray[j] = runYoutubeAnalyticsReport(channelId , videoId)
}
//Browser.msgBox("hellon");
nextPageToken = playlistResponse.nextPageToken;
}
create_cheet(idArray , titleArray , reportArray);
}
}
When I run the code, this is the error i get
Error: { "error": { "errors": [ { "domain": "youtube.part", "reason": "unknownPart", "message": "snippet?pageToken=CBkQAA", "locationType": "parameter", "location": "part" } ], "code": 400, "message": "snippet?pageToken=CBkQAA" } } (line 83, file "Youtube", project "YouTube")
I have been struggling on this issue for over a week now.. Hope i will get solution to this issue.. I Highly Appreciate Your Help and Response. Thanks You!

Writing Add-on manifest files

I created a GAS script file on my Drive using the example from Packt's book 'Learning Google Apps Script. My question is how do I make this into an Add-On. Not quite sure on the manifest file I need to create (even after reading https://developers.google.com/gmail/add-ons/how-tos/building).
function saveEmailAttachmentsToDrive(){
// Create 'Gmail Attachments' folder if not exists.
createFolder_('Gmail attachments');
// Get inbox threads starting from the latest one to 100.
var threads = GmailApp.getInboxThreads(0, 100);
var messages = GmailApp.getMessagesForThreads(threads);
var folderID = PropertiesService.getUserProperties()
.getProperty("FOLDER");
var file, folder = DriveApp.getFolderById(folderID);
for (var i = 0 ; i < messages.length; i++) {
for (var j = 0; j < messages[i].length; j++) {
if(!messages[i][j].isUnread()){
var msgId = messages[i][j].getId();
// Assign '' if MSG_ID is undefined.
var oldMsgId = PropertiesService.getUserProperties()
.getProperty('MSG_ID') || '';
if(msgId > oldMsgId){
var attachments = messages[i][j].getAttachments();
for (var k = 0; k < attachments.length; k++) {
PropertiesService.getUserProperties()
.setProperty('MSG_ID', messages[i][j].getId());
try {
file = folder.createFile(attachments[k]);
Utilities.sleep(1000);// Wait before next iteration.
} catch (e) {
Logger.log(e);
}
}
}
else return;
}
}
}
};
function createFolder_(name) {
var folder, folderID, found = false;
/*
* Returns collection of all user folders as an iterator.
* That means it do not return all folder names at once,
* but you should get them one by one.
*
*/
var folders = DriveApp.getFolders();
while (folders.hasNext()) {
folder = folders.next();
if (folder.getName() == name) {
folderID = folder.getId();
found = true;
break;
}
};
if (!found) {
folder = DriveApp.createFolder(name);
folderID = folder.getId();
};
PropertiesService.getUserProperties()
.setProperty("FOLDER", folderID);
return folderID;
}
My question is how do I make this into an Add-On. Not quite sure on the manifest file I need to create (even after reading https://developers.google.com/gmail/add-ons/how-tos/building).
The JSON for the manifest file for the gmail part is:
"gmail": {
"name": "My Gmail Add-on",
"logoUrl": "https://www.example.com/hosted/images/2x/my-icon.png",
"primaryColor": "#4285F4",
"secondaryColor": "#00BCD4",
"authorizationCheckFunction": "get3PAuthorizationUrls",
"contextualTriggers": [{
"unconditional": {},
"onTriggerFunction": "buildAddOn"
}]
That must be in addition to what is already there. A manifest file at it's very basic, looks like this:
{
"timeZone": "America/New_York",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER"
}
You are going to make it look like the following:
{
"timeZone": "America/New_York",
"dependencies": {
},
"gmail": {
"name": "My Gmail Add-on",
"logoUrl": "https://www.example.com/hosted/images/2x/my-icon.png",
"primaryColor": "#4285F4",
"secondaryColor": "#00BCD4",
"authorizationCheckFunction": "get3PAuthorizationUrls",
"contextualTriggers": [{
"unconditional": {},
"onTriggerFunction": "buildAddOn"
}]
},
"exceptionLogging": "STACKDRIVER"
}
Use your timeZone, and your settings. Note that there are no dependencies listed, but leave that part in.
manifest.json is generated automatically by the Apps Script engine. It is hidden by default, but you can view it by toggling View > manifest file to see the structure.
When you're ready to test the AddOn, you can go to Publish > Deploy as web add on. If you're uploading from the cloud editor, you do not need to add a separate manifest file in the web store listing.
The development documentation covers deployment methods in detail.

Filtering a Pivot Table in Google Scripts

This is my first post here and I am new to coding. I have been tasked with creating an automated report which will send a google form submitter a graph to help them monitor their production versus their daily goal. To do this I am using the new developer Google sheets script to refresh a pivot table. I found this code online, and it works great, however, I want to add a line which will filter based to the unique submitter's data. Here is the code I have so far:
function updatePivotTable() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pivotTableSheetName = "Lunch Chart";
var pivotTableSheetId = ss.getSheetByName(pivotTableSheetName).getSheetId();
var fields = "sheets(properties.sheetId,data.rowData.values.pivotTable)";
var sheets = Sheets.Spreadsheets.get(ss.getId(), {fields: fields}).sheets;
for (var i in sheets) {
if (sheets[i].properties.sheetId == pivotTableSheetId) {
var pivotTableParams = sheets[i].data[0].rowData[0].values[0].pivotTable;
break;
}
}
// Update source range:
pivotTableParams.source.endRowIndex = 40;
// Send back the updated params
var request = {
"updateCells": {
"rows": {
"values": [{
"pivotTable": pivotTableParams
}]
},
"start": {
"sheetId": pivotTableSheetId
},
"fields": "pivotTable"
}
};
Sheets.Spreadsheets.batchUpdate({'requests': [request]}, ss.getId());
}
Is this possible? Where would I add in the filter piece? I found this on the google developer site, but I am very new to coding so I don't really know where to put it or how to make it conditional. https://developers.google.com/sheets/api/reference/rest/v4/FilterCriteria
Thank you!
I am not sure if this is still relevant to you but you can add a filter using following piece of code-
"criteria": {
<col_index>: {"visibleValues": <filter criteria>},
<col_index>: {"visibleValues": <filter criteria>},
I've had the same problem and didn't find an easy explanation or code. Here is what I've done and it works:
function updatePivotTable() {
var ss = SpreadsheetApp.openById(SHEET_ID);
var pivotTableSheetName = "Pivot";
var pivotTableSheetId = ss.getSheetByName(pivotTableSheetName).getSheetId();
var fields = "sheets(properties.sheetId,data.rowData.values.pivotTable)";
var sheets = Sheets.Spreadsheets.get(ss.getId(), {fields: fields}).sheets;
for (var i in sheets) {
if (sheets[i].properties.sheetId == pivotTableSheetId) {
var pivotTableParams = sheets[i].data[0].rowData[0].values[0].pivotTable;
break;
}
}
// Update source range:
pivotTableParams.source.endRowIndex = 111;
pivotTableParams.criteria = { 3: {"visibleValues": ["foo"]}};
// Send back the updated params
var request = {
"updateCells": {
"rows": {
"values": [{
"pivotTable": pivotTableParams
}]
},
"start": {
"sheetId": pivotTableSheetId
},
"fields": "pivotTable",
}
};
Sheets.Spreadsheets.batchUpdate({'requests': [request]}, ss.getId());
}
So basically you need to pass the criteria as a pivotTableParams where the key of the object is the index of the column that you want to query and the value should be passed as another object with the format {"visibleValues": ["foo"]}.

Google Spreadsheet to pdf with watermark in Google script

I want to convert the spreadsheet with a watermark/background image, and send + save the generated pdf
The converting to pdf worked, but I don't know if/how you can put an image to the generated pdf.
This is what i got now:
function ExportAndSent(subject, filename, email) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var message = "A message";
var tempSpreadsheet = SpreadsheetApp.create(filename);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet = ss.getActiveSheet();
sheet.copyTo(tempSpreadsheet);
tempSpreadsheet.deleteActiveSheet();
var pdf = DriveApp.getFileById(tempSpreadsheet.getId()).getAs(MimeType.PDF);
var pdfBytes = pdf.getBytes();
var attach = {fileName:(filename + ".pdf"),content:pdfBytes, mimeType:MimeType.PDF};
// Here we need to put a watermark
// Send and export
MailApp.sendEmail(email, subject, message, {attachments:[attach]});
DriveApp.createFile(pdf);
// Delete Temporary
DriveApp.getFileById(tempSpreadsheet.getId()).setTrashed(true);
}
It looks like the spreadsheet is already converted to a PDF. A third party service can be used to add a watermark to open the PDF and add a watermark to it. PDF WebAPI is a free service you can use. Below I put together a function that can take in a PDF and add a watermark to it. The watermark is hardcoded to "watermark.jpg" currently, but that can be changed. From looking at the code above, the function should be called between the following lines:
var pdf = DriveApp.getFileById(tempSpreadsheet.getId()).getAs(MimeType.PDF);
var pdfBytes = pdf.getBytes();
The "pdf" variable should be used as input, and "pdfBytes", can take the return value of appendWatermark().
You will need to sign up for a free PDF WebAPI account to get an ID and key.
function appendWatermark(inputPDF) {
var fileBlob = inputPDF.copyBlob();
var decorationData = [{
"watermarkSettings": {
"opacity": 50,
"source": {
"file": "watermark.jpg",
"page": 1
},
"scale": {
"type": "relative",
"percent": 90
},
"location": "top",
"rotation": "0"
}
}];
var applicationData = {
"id": "",
"key": ""
};
var payload = {
"application": JSON.stringify(applicationData),
"input": fileBlob,
"decorationData": JSON.stringify(decorationData),
"resource": DriveApp.getFilesByName("watermark.jpg").next().getBlob()
};
var options = {
"method": "post",
"payload": payload
};
var response = UrlFetchApp.fetch("https://pdfprocess.datalogics.com/api/actions/decorate/document", options);
return response.getBytes;
}