Google Apps Script admin SDK speed - google-apps-script

i'm creating a google sheet which translates given data from schools to a admin SDK Upload to Google Apps.
I know there a create user limit of 10 per second, hence the 120ms Delay time.
but, when coloring each row in sheets which is processed the speed is around 500ms - 2 seconds per entry.
Which causes the script to stop at the maximum execution time, because there are more than 600 users to be added.
Where does it go wrong?
function UploadUsers() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Upload");
ss.setActiveSheet(sh);
var column = sh.getRange('A1:I5000');
var values = column.getValues(); // get all data in one call
var uploadRows = 0;
while ( values[uploadRows][0] != "" ) {
uploadRows++;
}
var i=0
var uiACU = SpreadsheetApp.getUi();
var ACUsersMessage0= "Upload Users";
var ACUsersMessage1= "Indien u op OK drukt worden er : "+ uploadRows + " Users aangemaakt! "
var result = uiACU.alert(
ACUsersMessage0,
ACUsersMessage1,
uiACU.ButtonSet.OK_CANCEL);
if (result == uiACU.Button.OK) {
for (i=0; i<uploadRows;i++){
var uniqueId=[i][0];
var mailAdress=values[i][3];
var voorNaam=values[i][1];
var achterNaam=values[i][2];
var Ou=values[i][8];
var Pass=values[i][4];
Utilities.sleep(12);
AdminDirectory.Users.insert ({
"kind": "admin#directory#user",
"password" : Pass,
"primaryEmail": mailAdress,
"orgUnitPath": Ou,
"changePasswordAtNextLogin": "TRUE",
"name": {
"givenName": voorNaam,
"familyName": achterNaam,
},
"externalIds": [
{
"value": uniqueId,
"type": "account",
"customType": "gappsUniqueId"
}
]
})
ss.getRange("D"+ (i+1)).setBackground("red")
}
} else {
//Full Stop
}
}

It goes wrong because every google script function has the 6minutes execution time, you can convey this in a couple of ways:
Read the best practices, the most important thing in there is to do thing in batches, instead of getting just a ROW and turn it RED, get several ROWs and do the sime, 1 ROW costs you 500ms, 20 ROWs will cost 505ms. There's probably a way for batch insert the users also, but I don't use the AdminSDK.
If there's no Batch for user insert, you can monitor the time of execution of the function in the beggining of the for(), if the time comes close the 6minutes (I recommend stopping at 5), save the last ROW inserted in the properties service, create a Progamatic Trigger that will run the function again in 7minutes, then paint the ROWs red. It will take a long time to run entirely, but will work.
function insertUsers(){
var timeStart = new Date().getTime();
var rowStart = PropertiesService.getScriptProperties().getProperty('lastRow') || 0;
for( from rowStart to endOfSheet ){
if( (new Date().getTime()) - timeStart > (5 * 60 * 1000) ){
PropertiesService.getScriptProperties().setProperty('lastRow', currentRow);
createTriggerToRun-insertUsers-in6Minutes;
return 1;
}
// code to insert users here
}
}

Related

multiple users using google spreadsheet as data entry form and Database, overwriting each others data

I want to make a sales entry system with google spreadsheet for multiple users.
User 1 will use the data entry form tab named "Main", using inputdata1()
User 2 will use the data entry form tab named "Sub", using inputdata2()
Each of them will write data in a new row that I found it by using the getlastrowspecial function (e.g. it this case lets say its row 10)
If both of them execute the code simultaneously. Sometimes I will see User 1's data being written on row 10 and User 2's data overwriting User 1's data in the same row on row 10. The checking to see if the row is empty before written is not working. I wanted to keep User 2 to wait until User 1's code is completely executed. I dont mind if User 2 need to call the function again.
Please help if there is any method to do this. Thank you very much!
var ss = SpreadsheetApp.getActiveSpreadsheet()
var db = ss.getSheetByName("DB")
var mainInput = ss.getSheetByName("Main")
var subInput = ss.getSheetByName("Sub")
function inputData1() {
var input1 = mainInput.getRange("E2:I2").getValues()
Logger.log(input1)
var lr = getLastRowSpecial(db, "A1:I")
Logger.log(lr)
if (db.getRange(lr + 1, 5).getValue() !== "") {
SpreadsheetApp.getUi().alert("Please try again")
return
} else {
db.getRange(lr + 1, 5, 1, input1[0].length).setValues(input1)
}
}
function inputData2() {
var input2 = subInput.getRange("E2:I2").getValues()
var lr = getLastRowSpecial(db, "A1:I")
if (db.getRange(lr + 1, 5).getValue() !== "") {
SpreadsheetApp.getUi().alert("Please try again")
return
} else {
db.getRange(lr + 1, 5, 1, input2[0].length).setValues(input2)
}
}
// following function getLastRowSpecial() for getting the last row with blank row"
function getLastRowSpecial(sheetlastrow, rangeString) {
var rng = sheetlastrow.getRange(rangeString).getValues();
var lrIndex;
for (var i = rng.length - 1; i >= 0; i--) {
lrIndex = i;
if (!rng[i].every(function (c) { return c == ""; })) {
break;
}
}
return lrIndex + 1;
}
Replace Range.setValues() with Sheet.appendRow(), like this:
current:
db.getRange(lr + 1, 5, 1, input1[0].length).setValues(input1)
new:
db.appendRow(input1[0]);
If you need the values to start in column E, use this:
db.appendRow([null, null, null, null].concat(input1[0]));
Alternatively, follow Cooper's advice and use the Lock Service. If you choose to go this route, also consider using the appendRows_() utility function.

Can a GAS Library be used a script template?

I've built a system within a spreadsheet that uses custom menus to move through a process. The script is stored in a template file, which is generated from a separate overview spreadsheet (via script) when required. Basically, each newly generated spreadsheet is a Product Tracker.
When the template is copied for a new Product Tracker the script is out-of-date if the template is updated. Would extracting the bound script from the template and creating a library from it make sense for my application?
I've nearly finished developing the template for a particular product, but I would like to roll it out for multiple products with slightly different templates which means it would be difficult to have a generic script that could be published within my Google Workspace Domain as an add-on (would probably need an add-on for each product).
Reading up on Libraries, Google warns that they can be slow. Currently, each script takes around 10 seconds to run and performs operations such as hide/show sheets, change tab colours, add/remove protection, send emails etc. There are already calls to another library, would this affect it as well? Would this be really slow as a library?
Thanks in advance!
Edit:
There are 10 buttons on the menu, one for each stage. The API's below are used at every stage. Reading through the execution logs, the scripts vary in execution time from 4 - 6 seconds depending on the stage.
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/script.container.ui"
],
Edit2:
Bastardised the code to remove certain information.
The script below is similar across around 9 scripts.
function doThing() {
var activeUser = Session.getActiveUser().getEmail();
var activeUserName = activeUser.split('#domain.com')[0]
// spreadsheets
const ss = SpreadsheetApp.getActive();
// sheets
const 1 = ss.getSheetByName("1.");
const 1b= ss.getSheetByName("1b.");
const 2 = ss.getSheetByName("2.");
const 3 = ss.getSheetByName("3.");
const 4 = ss.getSheetByName("4.");
const 5 = ss.getSheetByName("5.");
const 6 = ss.getSheetByName("6.");
const 7 = ss.getSheetByName("7.");
const 8 = ss.getSheetByName("8");
const 9 = ss.getSheetByName("9.");
const shipSheet = ss.getSheetByName("Shipping");
const buildEvalSheet = ss.getSheetByName("Build Evaluation");
// build Number
const buildNumber = 1.getRange(buildNumberCell).getValue();
// get the active user email address and log it
Logger.log("Active User Email: " + activeUser);
Logger.log("Running 3");
// this prevents non-authorised users from running the script
// see the permissionGroups object in the Variables script file for more information
// set a variable relative to the department using the buttons
var criteria = 'department'
// run the permissions function in the library
var automationPermission = EmailPermissionsLibrary.checkPermissions(activeUserName, criteria);
// run if true
if (automationPermission) {
// set tab colours for previous & current
1.setTabColor("GREEN");
1b.setTabColor("GREEN");
2.setTabColor("GREEN");
3.setTabColor("GREEN");
// protect
3.activate()
protect();
statusUpdate()
if (emailBoolean) {
// send information to Sales and Shipping
//create html email
var htmlEmail = "<html><body>";
htmlEmail += "<p>Hello,</p>"
htmlEmail += "<p>" + buildNumber + " message</p>"
// send email
MailApp.sendEmail({
to: 'email#domain.com',
subject: buildNumber + " message",
htmlBody: htmlEmail,
replyTo: activeUser
});
}
shipping()
buildEvalShow()
}
// error message if the user is not authorised
else {
SpreadsheetApp.getUi().alert('This account does not have authorisation')
Logger.log(activeUser + " does not have required permissions")
}
}
Will this script be slow as a library?
// set a variable relative to the department using the buttons
var criteria = 'department'
// run the permissions function in the library
var automationPermission = EmailPermissionsLibrary.checkPermissions(activeUserName, criteria);
// log permission
Logger.log(automationPermission)
// run if true
if (automationPermission) {
Logger.log("Running");
// show sheet
sheet.activate();
// check that the #### hasn't already ran by doing a row count
// if it hasn't, the following should return 1 which will be the titles
var sheetR = sheet.getLastRow();
if (sheetR <= 1) {
// summary table size
const sumTableRowCount = 12;
const sumTableColCount = 3;
for (i = 1; i <= 3; i++) {
var results = eval("prodQC" + [i] + "Sheet");
sumTable = sheet2.getRange(45, 2, sumTableRowCount, sumTableColCount).getValues();
var something = sheet2.getLastRow() + 2;
// create the title
var title = "Title " + i;
// write the title
sheet.getRange(sheetR, 1).setValue(title);
// Remove checks
if (i >= 2) {
var sheetRL= sheet.getLastRow();
sheet.getRange(buildEvalSheetLastRow, 4).removeCheckboxes();
sheet.getRange(buildEvalSheetLastRow, 6).removeCheckboxes();
}
// recount last row and add 1 to stop overwrite
var sheetR = sheet.getLastRow() + 1;
// write the QC summary table to the sheet
sheet.getRange(sheetR, 1, sumTableRowCount, sumTableColCount).setValues(sumTable);
// set checkboxes next to values on JIRA col
sheet.getRange(sheetR, 4, sumTableRowCount, 1).insertCheckboxes();
// set checkboxes next to values on the CPACC col
sheet.getRange(sheetR, 6, sumTableRowCount, 1).insertCheckboxes();
// add data validation to the Choose One col
// last row
var sheetR = sheet.getLastRow();
// set range to be from row 3, col 8, to last row
var range = sheet.getRange(4, 8, sheetR, 1);
// set the data validation to only accept values from the array
range .setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(false)
.requireValueInList(
[
"list item 1",
"list item 2",
"listed item 3"
],
true)
.build()
);
// add data validation to the Impact and Deadline cols
// set range to be from row 3, col 11, to last row for 2 cols
var cols = sheet.getRange(4, 11, sheetR, 2);
// set the data validation to only accept values from the array
cols .setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(false)
.requireValueInList(
[
"1",
"2",
"3",
"4",
"5"
],
true)
.build()
);
}
// remove all non-failures
// last row
var sheetR = sheet.getLastRow();
// range of values from A1, across 3 columns and to the last row
var range = sheet.getRange(1, 1, sheetR, 3);
// Default text from Production QC Template
var removeVal1 = "something";
// Modified text when the QC is complete
var removeVal2 = "something else";
// Default text from Production QC Template
var removeVal3 = "another thing";
// get values from the range to check in the For loop below
var rangeVals = range.getValues();
// Reverse the 'for' loop.
for (let i = rangeVals.length - 1; i >= 0; i--) {
// if matching values above are in the target range, delete the row
if (
rangeVals[i][2] === removeVal1 ||
rangeVals[i][2] === removeVal2 ||
rangeVals[i][2] === removeVal3) {
buildEvalSheet.deleteRow(i + 1)
}
}
} else { // if there is already more rows than expected).alert('this has done something');
}
} else { // error message if the user is not authorised
SpreadsheetApp.getUi().alert('This account does not have authorisation')
Logger.log(activeUser + " does not have required permissions")
}
}

Google sheet - Value is not being set correctly

I have setup a google sheet with a script attached to it that runs a function to pull chromebook device information, works great apart from two fields im interested in, "cpuStatusReports" and "diskVolumeReports" the script logger does show the correct info but my sheet cell values is set to for example
{cpuUtilizationPercentageInfo=[Ljava.lang.Object;#d151552,
cpuTemperatureInfo=[Ljava.lang.Object;#f57094c,
reportTime=2019-03-29T18:15:56.049Z}
my function is :-
function chromebookdetails2() { var domain, chromebooks, page, ss,
sheet, pageToken, i var sheetData4 =
onSheet.getSheetByName("chromebook") //sheetData4.clear(); domain =
"mydomainnamehere" chromebooks= new Array() do{ page =
AdminDirectory.Chromeosdevices.list("my_customer", {domain: domain,
maxResults: 1000, pageToken: pageToken }) for (i in
page.chromeosdevices){ chromebooks.push(page.chromeosdevices[i]) }
pageToken = page.nextPageToken }while(pageToken){ var row = 3
//starting row position for (var i = 0; i < chromebooks.length; i++) {
var sheetData4 = onSheet.getSheetByName("chromebook")
/////////////////////Header////////////////////////////////////////
var date = Utilities.formatDate(new Date(), "GMT+1", "dd/MM/yyyy")
sheetData4.getRange(1,1).setValue("Date Ran - "+date); //(2,1 means
2nd row, 1st column) sheetData4.getRange(2,1).setValue("orgUnitPath");
//(2,1 means 2nd row, 1st column)
sheetData4.getRange(2,2).setValue("annotatedUser");
sheetData4.getRange(2,3).setValue("annotatedLocation");
sheetData4.getRange(2,4).setValue("annotatedAssetId");
sheetData4.getRange(2,5).setValue("serialNumber");
sheetData4.getRange(2,6).setValue("lastEnrollmentTime");
sheetData4.getRange(2,7).setValue("deviceId");
sheetData4.getRange(2,8).setValue("bootMode");
sheetData4.getRange(2,9).setValue("recentUsers");
sheetData4.getRange(2,10).setValue("macAddress");
sheetData4.getRange(2,11).setValue("lastSync");
sheetData4.getRange(2,12).setValue("osVersion");
sheetData4.getRange(2,13).setValue("platformVersion");
sheetData4.getRange(2,14).setValue("activeTimeRanges");
sheetData4.getRange(2,15).setValue("model");
sheetData4.getRange(2,16).setValue("etag");
sheetData4.getRange(2,17).setValue("firmwareVersion");
sheetData4.getRange(2,18).setValue("status");
sheetData4.getRange(2,19).setValue("ethernetMacAddress");
sheetData4.getRange(2,20).setValue("notes");
sheetData4.getRange(2,21).setValue("systemRamTotal");
sheetData4.getRange(2,22).setValue("CPU");
if(chromebooks[i].length == 0){
// array is empty Logger.log('empty'); } else { //array not empty >Logger.log('not empty');
Logger.log(chromebooks[i].cpuStatusReports);
/////////////////////Array Data///////////////////////////////////
sheetData4.getRange(row,1).setValue(chromebooks[i].orgUnitPath);
sheetData4.getRange(row,2).setValue(chromebooks[i].annotatedUser);
sheetData4.getRange(row,3).setValue(chromebooks[i].annotatedLocation);
sheetData4.getRange(row,4).setValue(chromebooks[i].annotatedAssetId);
sheetData4.getRange(row,5).setValue(chromebooks[i].serialNumber);
sheetData4.getRange(row,6).setValue(chromebooks[i].lastEnrollmentTime);
sheetData4.getRange(row,7).setValue(chromebooks[i].deviceId);
sheetData4.getRange(row,8).setValue(chromebooks[i].bootMode);
sheetData4.getRange(row,9).setValue(chromebooks[i].recentUsers);
sheetData4.getRange(row,10).setValue(chromebooks[i].macAddress);
sheetData4.getRange(row,11).setValue(chromebooks[i].lastSync);
sheetData4.getRange(row,12).setValue(chromebooks[i].osVersion);
sheetData4.getRange(row,13).setValue(chromebooks[i].platformVersion);
sheetData4.getRange(row,14).setValue(chromebooks[i].activeTimeRanges);
sheetData4.getRange(row,15).setValue(chromebooks[i].model);
sheetData4.getRange(row,16).setValue(chromebooks[i].etag);
sheetData4.getRange(row,17).setValue(chromebooks[i].firmwareVersion);
sheetData4.getRange(row,18).setValue(chromebooks[i].status);
sheetData4.getRange(row,19).setValue(chromebooks[i].ethernetMacAddress);
sheetData4.getRange(row,20).setValue(chromebooks[i].notes);
sheetData4.getRange(row,21).setValue(chromebooks[i].systemRamTotal /
(1024*1024) /1024); // "/ (1024*1024)" converts bytes to Mb "/1024"
then converts back to Gb
sheetData4.getRange(row,22).setValue(chromebooks[i].cpuStatusReports);
}
row++ Logger.log(row) } } }
The results in the log file....
[{cpuUtilizationPercentageInfo=[63],
cpuTemperatureInfo=[{temperature=24, label=soc_dts0 },
{temperature=24, label=soc_dts1 }],
reportTime=2019-03-10T18:11:49.480Z}]
How do i reference cpuUtilizationPercentageInfo, cpuTemperatureInfo from cpuStatusReports?
Thankyou
In the loop add
var status = chromebooks[i].cpuStatusReports;
var cpuUtilizationPercentageInfo = status[0].cpuUtilizationPercentageInfo[0];
var cpuTemperatureInfo = status[0].cpuTemperatureInfo[0].temperature;
var label = status[0].cpuTemperatureInfo[0].label;
Check logger.log() to see if you get the desired results.
did you tried:
.setValue(chromebooks[i].cpuStatusReports.toString());
just turn every thing into string and you should have no problem on setValue() I guess.

How to check Gmail Thread for Replies from Email

I am creating a basic CRM that needs to mark when a thread has been replied to.
I have created a script that can scan my inbox for threads from a list of emails in a sheet, check the last message in each thread and collect the .getFrom in order to see if I was the last to reply.
However, I can't figure out how to check if there has been a response from the person who's been contacted throughout the whole thread.
Here's the script that checks for the last message. (It's an extract of a larger script in case any references are missing):
Example Sheet
function UpdateStatus() {
// Connect to our active sheet and collect all of our email addresses in column G
var sheet = SpreadsheetApp.getActiveSheet();
var totalRows = sheet.getLastRow();
var range = sheet.getRange(2, COLUMN_WITH_EMAIL_ADDRESSES, totalRows, 1);
var emails = range.getValues();
// Attempt to iterate through 100 times (although we'll timeout before this)
for (var cntr = 0; cntr<100; cntr++ ) {
// If we've reached the end of our last, wrap to the front
if (lastRowProcessed >= totalRows) lastRowProcessed = 1;
// Increment the row we're processing
var currentRow = lastRowProcessed+1;
// Get the email address from the current row
var email = emails[currentRow-2][0];
// If the email address field is empty, skip to the next row
if (!email) {
lastRowProcessed = currentRow;
cache.put("lastRow", currentRow, 60*60*24);
continue;
}
// Look for all threads from me to this person
var threads = GmailApp.search('from:me to:'+email);
// If there are no threads, I haven't emailed them before
if (threads.length == 0) {
// Update the spreadsheet row to show we've never emailed
var range = sheet.getRange(currentRow,13, 1, 4 ).setValues([["NEVER", "", "", ""]] );
// And carry on
lastRowProcessed = currentRow;
cache.put("lastRow", currentRow, 60*60*24); // cache for 25 minutes
continue;
}
// Beyond a reasonable doubt
var latestDate = new Date(1970, 1, 1);
var starredMsg = "";
var iReplied = ""
// Iterate through each of the message threads returned from our search
for (var thread in threads) {
// Grab the last message date for this thread
var threadDate = threads[thread].getLastMessageDate();
// If this is the latest thread we've seen so far, make note!
if (threadDate > latestDate) {
latestDate = threadDate;
// Check to see if we starred the message (we may be back to overwrite this)
if (threads[thread].hasStarredMessages()) {
starredMsg = "★";
} else {
starredMsg = "";
}
// Open the thread to get messages
var messages = threads[thread].getMessages();
// See who was the last to speak
var lastMsg = messages[messages.length-1];
var lastMsgFrom = lastMsg.getFrom();
// Use regex so we can make our search case insensitive
var re = new RegExp(email,"i");
// If we can find their email address in the email address from the last message, they spoke last
// (we may be back to overwrite this)
if (lastMsgFrom.search(re) >= 0) {
iReplied = "NO";
} else {
iReplied = "YES";
}
}

getMessageById() slows down

I am working on a script that works with e-mails and it needs to fetch the timestamp, sender, receiver and subject for an e-mail. The Google script project has several functions in separate script files so I won't be listing everything here, but essentially the main function performs a query and passes it on to a function that fetches data:
queriedMessages = Gmail.Users.Messages.list(authUsr.mail, {'q':query, 'pageToken':pageToken});
dataOutput_double(sSheet, queriedMessages.messages, queriedMessages.messages.length);
So this will send an object to the function dataOutput_double and the size of the array (if I try to get the size of the array inside the function that outputs data I get an error so that is why this is passed here). The function that outputs the data looks like this:
function dataOutput_double(sSheet, messageInfo, aLenght) {
var sheet = sSheet.getSheets()[0],
message,
dataArray = new Array(),
row = 2;
var i, dateCheck = new Date;
dateCheck.setDate(dateCheck.getDate()-1);
for (i=aLenght-1; i>=0; i--) {
message = GmailApp.getMessageById(messageInfo[i].id);
if (message.getDate().getDate() == dateCheck.getDate()) {
sheet.insertRowBefore(2);
sheet.getRange(row, 1).setValue(message.getDate());
sheet.getRange(row, 2).setValue(message.getFrom());
sheet.getRange(row, 3).setValue(message.getTo());
sheet.getRange(row, 4).setValue(message.getSubject());
}
}
return;
};
Some of this code will get removed as there are leftovers from other types of handling this.
The problem as I noticed is that some messages take a long time to get with the getMessageById() method (~ 4 seconds to be exact) and when the script is intended to work with ~1500 mails every day this makes it drag on for quite a while forcing google to stop the script as it takes too long.
Any ideas of how to go around this issue or is this just something that I have to live with?
Here is something I whipped up:
function processEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var messages = Gmail.Users.Messages.list('me', {maxResults:200, q:"newer_than:1d AND label:INBOX NOT label:PROCESSED"}).messages,
headers,
headersFields = ["Date","From","To","Subject"],
outputValue=[],thisRowValue = [],
message
if(messages.length > 0){
for(var i in messages){
message = Gmail.Users.Messages.get('me', messages[i].id);
Gmail.Users.Messages.modify( {addLabelIds:["Label_4"]},'me',messages[i].id);
headers = message.payload.headers
for(var ii in headers){
if(headersFields.indexOf(headers[ii].name) != -1){
thisRowValue.push(headers[ii].value);
}
}
outputValue.push(thisRowValue)
thisRowValue = [];
}
var range = ss.getRange(ss.getLastRow()+1, ss.getLastColumn()+1, outputValue.length, outputValue[0].length);
range.setValues(outputValue);
}
}
NOTE: This is intended to run as a trigger. This will batch the trigger call in 200 messages. You will need to add the label PROCESSED to gmail. Also on the line:
Gmail.Users.Messages.modify( {addLabelIds:["Label_4"]},'me',messages[i].id);
it shows Label_4. In my gmail account "PROCESSED" is my 4th custom label.