Last modified date in Google App Script GAS sheets - google-apps-script

I am trying to get the last modified date of a sheet from a GAS add on which I am developing.
My current idea is to get the Drive revision list and then take the last value. This seems a bit overkill for just getting the last modified, I am also worried that this will break if the number of revisions exceeds 1000 as per this link suggests.
https://developers.google.com/drive/api/v3/reference/revisions/list
Ideally I would like to know the range which has changed too, but I do not think this is possible.
I cannot use the onEdit event because I would like to track edits made by users who have not installed the add-on.
var fileId = SpreadsheetApp.getActiveSpreadsheet().getId();
var revisions = Drive.Revisions.list(fileId);
var revisionLength = revisions.items.length;
if(revisionLength > 0){
var revision = revisions.items[revisionLength-1];
var date = new Date(revision.modifiedDate);
Logger.log(date.toString());
}

I believe you can do that as follow
var lastUpdated = DriveApp.getFileById(fileId).getLastUpdated();
See function reference

Given that you need users of your add-on to have access to revision information from non-add-on users, the Drive revision list is precisely what you need. Happily, you can get the content of revisions, so if you wish you can compute diffs. I don't know what your data looks like, so that might be easy or nigh-impossible.
Aside: to your point about more than 1000 revisions, if there are more than 1000 (or whatever your page size is) revisions, you'll get a nextPageToken like so:
{
"kind": "drive#revisionList",
"nextPageToken": "BHNMJKHJKHKVJHyugaiohasdzT1JyUmlQWG10RUJ1emx1S2xNDg4EgQzMzY1GAI=",
"revisions": [
...
]
}
If you see that you'll need to list revisions again, providing that token.
Anyway, when you list revisions, each revision will look something like this:
{
"kind": "drive#revision",
"etag": "\"som3-e-tAg\"",
"id": "3365",
"selfLink": "https://www.googleapis.com/drive/v2/files/dummydummydummy/revisions/3365",
"mimeType": "application/vnd.google-apps.spreadsheet",
"modifiedDate": "2018-10-19T19:05:41.762Z",
"published": false,
"exportLinks": {
"application/x-vnd.oasis.opendocument.spreadsheet": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=ods",
"text/tab-separated-values": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=tsv",
"application/pdf": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=pdf",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=xlsx",
"text/csv": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=csv",
"application/zip": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=zip",
"application/vnd.oasis.opendocument.spreadsheet": "https://docs.google.com/spreadsheets/export?id=dummydummydummy&revision=3365&exportFormat=ods"
},
"lastModifyingUserName": "Joe User",
"lastModifyingUser": {
"kind": "drive#user",
"displayName": "Joe User",
"picture": {
"url": "https://lh3.googleusercontent.com/-asdfsadf/AAAAAAAAAAI/AAAAAAAAFOk/OIPUYOIUGO/s64/photo.jpg"
},
"isAuthenticatedUser": true,
"permissionId": "123456789",
"emailAddress": "user#gmail.com"
}
}
Provided your data isn't insanely complex or large, you could fetch the target the text/csv export link for the revisions you wish to compare, and then do that comparison in Apps Script.
That might look something like:
var fileId = SpreadsheetApp.getActiveSpreadsheet().getId();
var revisions = Drive.Revisions.list(fileId);
var revisionLength = revisions.items.length;
if(revisionLength > 1) { // something to compare!
var revision = revisions.items[revisionLength-1];
var newContent = UrlFetchApp.fetch(revision.exportLinks["text/csv"]).getContent();
newContent = Utilities.parseCsv(newContent);
var oldRevision = revisions.items[revisionLength-2];
var oldContent = UrlFetchApp.fetch(oldRevision.exportLinks["text/csv"]).getContent();
oldContent = Utilities.parseCsv(oldContent);
# TODO check they're the same size!
# where do they differ?
for (var row = 0; row < newContent.length; row++) {
for (var col = 0; col < newContent[0].length; col++) {
if (newContent[row][col] != oldContent[row][col]) {
Logger.log('Change on row ' + (row + 1) + ' column ' + (col + 1));
}
}
# when did it change?
var date = new Date(revision.modifiedDate);
Logger.log(date.toString());
}

Related

Can I list all the times I saved a file?

My company uses Google Drive and we are still mainly using Microsoft Office documents.
Is it possible to see my activity or each time I saved a document in the Shared Drive even though it is not Google Documents I want to see the activity of?
I found this link https://developers.google.com/apps-script/advanced/drive-activity , but the code only returns Google Docs activity. Not non-Google documents like Word and Excel.
You might want to check if using Revisions.list will fit your needs.
Revisions.list
Lists the current file's revisions.
Path parameters:
fileId - The ID of the file.
Optional query parameters:
fields - The paths of the fields you want included in the response. If not specified, the response includes a default set of fields specific to this method. For development you can use the special value * to return all fields, but you'll achieve greater performance by only selecting the fields you need. For more information, see Return specific fields for a file.
pageSize - The maximum number of revisions to return per page. Acceptable values are 1 to 1000, inclusive. (Default: 200)
pageToken - The token for continuing a previous list request on the next page. This should be set to the value of 'nextPageToken' from the previous response.
Response Body:
{
"kind": "drive#revisionList",
"nextPageToken": string,
"revisions": [
revisions Resource
]
}
Revisions Resource Representation:
You can obtain useful information related to the revision done to the file such as the modified time and last modifying user.
{
"kind": "drive#revision",
"id": string,
"mimeType": string,
"modifiedTime": datetime,
"keepForever": boolean,
"published": boolean,
"publishedLink": string,
"publishAuto": boolean,
"publishedOutsideDomain": boolean,
"lastModifyingUser": {
"kind": "drive#user",
"displayName": string,
"photoLink": string,
"me": boolean,
"permissionId": string,
"emailAddress": string
},
"originalFilename": string,
"md5Checksum": string,
"size": long,
"exportLinks": {
(key): string
}
}
You can specify specific fields in your request under fields parameter so that only necessary information can be shown in the response body:
Sample Fields Parameter:
nextPageToken, revisions/id, revisions/modifiedTime, revisions/lastModifyingUser/displayName, revisions/lastModifyingUser/emailAddress
Sample Response Body:
{
"revisions": [
{
"id": "1898",
"modifiedTime": "2020-12-16T22:29:02.971Z",
"lastModifyingUser": {
"displayName": "User1 Test",
"emailAddress": "user1#example.com"
}
}
]
}
Play with DriveApps file.getLastUpdated(). This is not the same as ALL the times you've updated it but it should get the last time the file was changed. https://developers.google.com/apps-script/reference/drive/file#getLastUpdated()
Or do you really need a list of all the edit times not just the most recent one? In that case you could run a script once a day that records the lastUpdated for all the files you care about or all the files in a folder and record if they've changed. What is the use case?
Thanks for that Ron. Revisions did the trick.
It took me a while to wrap my head around it, but these are the 2 functions I used. I will try to put them together at some point when I have Drive API v3.
function listFilesInFolder() {
// get the id's of files in a folder
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(["Name", "File-Id"]);
var folder = DriveApp.getFolderById(" ID STRING ");
var contents = folder.getFiles();
var counter = 0;
var file;
while (contents.hasNext()) {
var file = contents.next();
counter++;
data = [
file.getName(),
file.getId(),
];
sheet.getRange("C2").setValue = counter
sheet.appendRow(data);
};
};
function fileactivity() {
var revs = Drive.Revisions.list(" FileID String ");
var savedList = [];
for(var i=0; i<revs.items.length; i++) {
var revision = revs.items[i];
// modifiedByMeTime requires Drive API v3
savedList.push([revision.kind, revision.modifiedDate]);
};
var sheet = SpreadsheetApp.getActiveSheet();
Logger.log(savedList);
}

google-apps-script | TypeError for getEvents()

I have a calendar called 'IMPORTANT!!' which when subbed into my code causes it not to work (there is no error, the function just does nothing). I think this problem is caused by the !! characters at the end of the name, but I do not know what that problem is or how to fix it. What should I do to avoid this problem?
function myFunction() {
var year = 2019;
var month = 0;
var fromDate = new Date(year,month,1,0,0,0);
var toDate = new Date(year,month,28,0,0,0);
var theCalendar = CalendarApp.getCalendarsByName('IMPORTANT!!')[0];
var events = theCalendar.getEvents(fromDate, toDate);
for(var i = 0; i < events.length; i++){
var ev = events[i];
ev.deleteEvent();
}
}
How do I fix the error so my script will delete all of the events from the google calendar posted in 'def' in january of 2019.
getCalendarsByName() returns Calendar[] which is an array. So if the calendar name of def is only one in your calendars, how about following modification?
From:
var theCalendar = CalendarApp.getCalendarsByName('def');
To:
var theCalendar = CalendarApp.getCalendarsByName('def')[0];
Note:
If you have several calendars with same calendar name, I recommend to use the calendar ID.
As a point, when the method name is seen, it is found that "Calendars" of "getCalendarsByName" is plural. By this, it might notice that several values will be returned.
Reference:
getCalendarsByName()
Edit 1:
About your new question, if events has not elements, the for loop doesn't work.
I think that this might be the reason of your new issue. Please confirm about this.
When events can be retrieved by theCalendar.getEvents(fromDate, toDate). eleteEvent() in the for loop will work.
Edit 2:
You can retrieve the calendar IDs and calendar names of all calendars using the following script.
Sample script:
var result = CalendarApp.getAllCalendars().map(function(e) {return {id: e.getId(), name: e.getName()}});
Logger.log(result);
Result:
[
{
"id": "### calendarId1 ###",
"name": "### calendarName1 ###"
},
{
"id": "### calendarId2 ###",
"name": "### calendarName2 ###"
},
,
,
,
]

Is there a setTitle() similar to getTitle() of a page element?

As the title suggests, I'm looking for a way to set the alt title of an image in a slideshow.
Currently this is what i have tried, but for some reason it doesn't seem to update:
var resource = {"requests": [
{"updatePageElementAltText": {
"objectId": id,
"description": "",
"title": elementTitle
}
}]};
Slides.Presentations.batchUpdate(resource, presentationId);
It might be worth noting that the script is running in the Script Editor of a google sheet. The variables id, elementTitle and presentationId are all defined earlier in the script and I've checked that they are correct.
Can anyone spot the issue with this or suggest an easier way to do it?
Edit: Tanaike helped me make this specific part of the script work, but it isn't working in the larger picture, hence this edit.
What the script is supposed to do, is basically do a find/replace on all Image elements in the slideshow.
Based on keys in a sheet in Column A it should replace the Image URL in with the corresponding URL in column B. The script then cycles through all elements in the slideshow, finds the images, and then cycles through them to check if any of the titles have the 'key' as the title. The image URL should then be replaced with the URL on the same row in sheet. This part of the script is tested and works, but the key is removed from the object when the URL is updated. This shouldn't be happening as the Image should be able to be replaced again later.
For this reason, I tried to save the title before updating the URL and the put it back with the above-mentioned batchUpdate, but for some reason, it isn't working properly.
Here is the full script:
function imageReplacer() {
var newPresentationSlides = SlidesApp.openByUrl(myslidesurl).getSlides();
var imageTitles = SpreadsheetApp.openByUrl(mysheeturl).getRange("'Image Replace List'!A2:A").getValues();
var imageURLs = SpreadsheetApp.openByUrl(mysheeturl).getRange("'Image Replace List'!B2:B").getValues();
var presentationId = 'myslidesid';
for (y = 0; y < newPresentationSlides.length; y++) {
var pageElements = newPresentationSlides[y].getPageElements();
for (x = 0; x < pageElements.length; x++) {
for (a = 0; a < imageTitles.filter(String).length; a++) {
if (pageElements[x].getPageElementType() == "IMAGE") {
if(pageElements[x].asImage().getTitle() == imageTitles[a]) {
var elementTitle = pageElements[x].asImage().getTitle();
var id = pageElements[x].getObjectId();
pageElements[x].asImage().replace(imageURLs[a]);
var id = pageElements[x].getObjectId();
var resource = {"requests": [
{"updatePageElementAltText": {
"objectId": id,
"description": "Sample description",
"title": elementTitle
}
}]};
Slides.Presentations.batchUpdate(resource, presentationId);
}
}
}
}
}
}
As you can see the middle part of the script is exactly the same as tanaike suggested, but it's just not working properly (I even tested that specific part as a stand-alone script and it worked fine.).
Second edit:
Examples:
Sheet: https://docs.google.com/spreadsheets/d/1npWyONio_seI3bRibFWxiqzHxLZ-ie2wbszgROkLduE/edit#gid=0
Slides: https://docs.google.com/presentation/d/1rfT7TLD-O7dBbwV5V3UbugN1OLOnBI2-CZN2GPnmANM/edit#slide=id.p
I think that your script works. You can confirm the updated result on the slide.
But if you want to retrieve the title and description using Slides services like getTitle() and getDescription() after the title and description are updated using Slides API, it seems that those results are not updated. The updated results couldn't be retrieved even if saveAndClose() is used. And also, unfortunately, in the current stage, I couldn't find the methods like setTitle() and setDescription() in my environment. So how about this workaround? In this workaround, the title and description are updated by Slides API and those are retrieved by Slides API.
Sample script:
var presentationId = "###"; // Please set this.
var objectId = "###"; // Please set this.
// Update title and description
var resource = {"requests": [
{"updatePageElementAltText": {
"objectId": objectId,
"description": "Sample description",
"title": "Sample title"
}
}]};
Slides.Presentations.batchUpdate(resource, presentationId);
// Retrieve updated title and description
var res = Slides.Presentations.get(presentationId);
var slides = res.slides;
for (var i = 0; i < slides.length; i++) {
var pe = slides[i].pageElements;
for (var j = 0; j < pe.length; j++) {
if (pe[j].objectId == objectId) {
Logger.log(pe[j].title)
Logger.log(pe[j].description)
break;
}
}
}
Note:
If you use this script, please enable Slides API at Advanced Google Services and API console.
References:
presentations.batchUpdate
presentations.get
If I misunderstand what you want, I'm sorry.
Edit:
You want to replace all images in Slides.
At this time, you want to search the title of each image and replace the image from URL using the title.
When the images are replaced, you don't want to change the title (key) of each image.
If my understanding is correct, how about this modification?
Modification points:
It seems that when the image is replaced, the title of image is cleared.
In order to avoid this, when the image is replaced, it also puts the title. For this situation, batchUpdate of Slides API is used.
From the viewpoint of the process cost, at first, it creates the request body and requests the request body. By this, this situation can be achieved by only one API call.
Modified script:
function imageReplacer() {
var spreadsheetId = "### spreadsheetId ###"; // Please modify this.
var sheetName = "Image Replace List";
var presentationId = "### presentationId ###"; // Please modify this.
var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
var values = sheet.getRange(2, 1, sheet.getLastRow(), 2).getValues().filter(function(e) {return e[0] && e[1]});
var s = SlidesApp.openById(presentationId);
var slides = s.getSlides();
var requests = slides.reduce(function(reqs, slide) {
var r = slide.getPageElements().reduce(function(ar, e) {
if (e.getPageElementType() == "IMAGE") {
var key = values.filter(function(v) {return v[0] == e.getTitle()});
if (key.length == 1) {
var id = e.getObjectId();
var rq = [
{"replaceImage":{"imageObjectId":id, "url": key[0][1]}},
{"updatePageElementAltText":{"objectId":id, "title": key[0][0]}}
];
Array.prototype.push.apply(ar, rq);
}
}
return ar;
}, []);
if (r.length > 0) Array.prototype.push.apply(reqs, r);
return reqs;
}, []);
Slides.Presentations.batchUpdate({requests: requests}, presentationId);
}
Note:
I'm not sure about the maximum number of requests for one API call. So if you want to replace a lot of images, if the error due to this occurs, please modify above script.

Extract Keenio Data into google spreadsheet

I am currently using ImportJSON to import Sendgrid Email with data Keenio Extraction Query API URL by calling the ImportJSON function in a Google Spreadsheet cell of Sheet DATA.
=ImportJSON("https://api.keen.io/3.0/projects/"& PROJECT_KEY & "/queries/extraction?api_key=" & API_KEY & "&event_collection=" & EVT_COL & "&timezone=" & TIMEZONE & "&latest=" & LATEST & "&property_names..........", PTDATA!$AB$1)
In Sheet PTDATA, in the last column cell i am setting a random number for ImportJSON to recalculate. The function runs on Spreadsheet open event. I have also added a custom menu to call the ReCalcCell custom function.
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('IMPORT DATA')
.addItem('KEENIO DATA', 'ReCalcCell')
.addToUi();
}
function ReCalcCell(){
var min = Math.ceil(0);
var max = Math.floor(9999);
var randomNum = Math.floor(Math.random() * (max - min + 1)) + min
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("PTDATA");
sh.getRange("$AB$1").setValue(randomNum);
}
PTDATA sheet has specific column header names for which i want to pull the data from DATA sheet. Towards the right of these columns, i have other calculation columns which work on these specific columns.
Since the columns in DATA sheet always appear in a random / shuffled order, i had to write a small custom function GCL which takes in a header name and returns its datarange address from DATA sheet as a string.
function GCL(header,dummy) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("DATA");
var headings = sheet.getRange(1, 1, 1, sheet.getLastColumn()); // get the range representing the whole sheet
var width = headings.getWidth();
var lrow = sheet.getLastRow();
// search every cell in row 1 from A1 till the last column
for (var i = 1; i <= width; i++) {
var data = headings.getCell(1,i).getValue();
if (data == header) {
return ((sheet.getSheetName() + "!" + columnToLetter(i)+"2:" + columnToLetter(i) + lrow).toString()); // return the column range if we find it
break; // exit when found
}
}
return(-1); // return -1 if it doesn't exist
}
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
Then i use the custom function GCL in each specific column to get it's datarange. Once data is populated, the PDATA sheet is used to create different Pivots for reporting purposes.
=ARRAYFORMULA(INDIRECT(GCL(A1,$AB$1)))
The problems i am facing is that though the ImportJSON data populates the DATA sheet:
DATA Sheet:
The columns appear shuffled everytime, so my calculation columns cannot calculate as the references go away. This renders the pivots useless! To counter this issue, i had to create the PDATA sheet to pull in specific columns using the custom function GCL.
The custom function GCL does not always refresh and most of the time shows #Ref error.
PDATA Sheet:
BTW, my JSON output from Keenio looks like this:
{
"result":
[
{
"sg_event_id": "92-OndRfTs6fZjNdHWzLBw",
"timestamp": 1529618395,
"url": "https://noname.com?utm_campaign=website&utm_source=sendgrid.com&utm_medium=email",
"ip": "192.168.1.1",
"event": "click",
"keen": {
"timestamp": "2018-06-21T21:59:55.000Z",
"created_at": "2018-06-21T22:00:28.532Z",
"id": "555c1f7c5asdf7000167d87b"
},
"url_offset": {
"index": 38,
"type": "text"
},
"sg_message_id": "F5mwV1rESdyKFA_2bn1IEQ.filter0042p3las1-15933-5B2A68E8-36.0",
"useragent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
"email": "no.name#noname.com"
}, {
"sg_event_id": "bjMlfsSfRyuXEVy8LndsYA",
"timestamp": 1529618349,
"url": "https://noname.com?utm_campaign=website&utm_source=sendgrid.com&utm_medium=email",
"ip": "192.168.1.1",
"event": "click",
"keen": {
"timestamp": "2018-06-21T21:59:09.000Z",
"created_at": "2018-06-21T21:59:39.491Z",
"id": "555c1f7c5asdf7000167d87b"
},
"url_offset": {
"index": 36,
"type": "text"
},
"sg_message_id": "F5mwV1rESdyKFA_2bn1IEQ.filter0042p3las1-15933-5B2A68E8-36.0",
"useragent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
"email": "no.name#noname.com"
}, {
"sg_event_id": "fru_s2s1RtueuqBMNoIoTg",
"timestamp": 1529618255,
"url": "https://noname.com?utm_campaign=website&utm_source=sendgrid.com&utm_medium=email",
"ip": "192.168.1.1",
"event": "click",
"keen": {
"timestamp": "2018-06-21T21:57:35.000Z",
"created_at": "2018-06-21T21:58:20.374Z",
"id": "555c1f7c5asdf7000167d87b"
},
"url_offset": {
"index": 29,
"type": "text"
},
"sg_message_id": "F5mwV1rESdyKFA_2bn1IEQ.filter0042p3las1-15933-5B2A68E8-36.0",
"useragent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
"email": "no.name#noname.com"
}
]
}
My questions are:
Is there a way to parse the JSON result without use of ImportJSON, which has to be entered as a custom function in a cell that also depends on recalculation? ImportJSON sometimes doesn't work properly.
How can this code be refactored or optimized so that it can always return data to PDATA sheet columns?
Is there a better way of accomplishing what i want without resorting to custom functions like GCL in the PDATA Sheet or ImportJSON in DATA sheet?
How about this sample script? This script parses the values retrieved from API using UrlFetchApp and put them to the sheet "DATA". You can run this at the menu of spreadsheet. Before you run this, please put the endpoint.
Sample script :
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('IMPORT DATA')
.addItem('KEENIO DATA', 'ReCalcCell')
.addItem('main', 'main')
.addToUi();
}
function main() {
var url = "###"; // Please put the endpoint with your token.
var res = UrlFetchApp.fetch(url).getContentText(); // Modified
var values = JSON.parse(res);
var putData = values.result.map(function(e) {return [e.useragent, e.sg_event_id, e.timestamp, e.ip, e.url, e.event, e.keen.timestamp, e.keen.created_at, e.keen.id, e.url_offset.index, e.url_offset.type, e.sg_message_id, e.email]});
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("DATA");
sheet.getRange(sheet.getLastRow() + 1, 1, putData.length, putData[0].length).setValues(putData);
}
Note :
When you use this, please put the endpoint including your token to url.
I confirmed this script using the JSON object in your question. So if the structure of the object is changed, it is required to also modify the script. Please be careful this.
Reference :
UrlFetchApp.fetch()
If I misunderstand about your issue, please tell me. I would like to modify it.
Edit 1 :
Pattern 1 :
var putData = values.result.map(function(e) {return [e.useragent, e.sg_event_id, e.timestamp, e.ip, e.url, e.event, e.keen.timestamp, e.keen.created_at, e.keen.id, JSON.parse(e["url_offset"]).index, JSON.parse(e["url_offset"]).type, e.sg_message_id, e.email]});
Pattern 2 :
var putData = values.result.map(function(e) {return [e.useragent, e.sg_event_id, e.timestamp, e.ip, e.url, e.event, e.keen.timestamp, e.keen.created_at, e.keen.id, e["url_offset"].index, e["url_offset"].type, e.sg_message_id, e.email]});
Edit 2 :
Could you please run this script and provide the values of the created file? Of course, please remove the personal information. But please don't modify the structure of the object. If you cannot do it, I would like to think of other ways.
var url = "###"; // Please put the endpoint with your token.
var res = UrlFetchApp.fetch(url).getContentText();
DriveApp.createFile("sample.txt", res, MimeType.PLAIN_TEXT)
Edit 3 :
Please copy and paste this script in your script editor, run myFunction(). Then, please show the values of file. When you run this function, please confirm whether there are NOT the same function name in your project.
function myFunction() {
var url = "###"; // Please put the endpoint with your token.
var res = UrlFetchApp.fetch(url).getContentText();
DriveApp.createFile("sample.txt", res, MimeType.PLAIN_TEXT)
}
Edit 4 :
Please copy and paste this script in your script editor, run myFunction2(). Then, please show the results. When you run this function, please confirm whether there are NOT the same function name in your project.
Please confirm whether the keys and values of keen and url_offset are retrieved.
function myFunction2() {
var url = "###";
var res = UrlFetchApp.fetch(url).getContentText();
var values = JSON.parse(res);
for (var key in values.result[0]) {
Logger.log("key: %s, value: %s", key, values.result[0][key])
if (typeof values.result[0][key] == "object") {
for (var dkey in values.result[0][key]) {
Logger.log("key: %s, dkey: %s, value: %s", key, dkey, values.result[0][key][dkey])
}
}
}
}
Edit 5 :
Please copy and paste this script in your script editor, run myFunction3(). Then, please show the results. When you run this function, please confirm whether there are NOT the same function name in your project.
function myFunction3() {
var url = "###"; // Please set this.
var res = UrlFetchApp.fetch(url).getContentText();
var values = JSON.parse(res);
var obj = [];
for (var i = 0; i < values.result.length; i++) {
var temp = {};
var v = values.result[i];
for (var key in v) {
temp[key.replace(/_/g, "")] = v[key];
if (typeof v[key] == "object") {
for (var dkey in v[key]) {
temp[key.replace(/_/g, "") + dkey.replace(/_/g, "")] = v[key][dkey];
}
}
}
obj.push(temp);
}
var putData = obj.map(function(e) {return [e.useragent, e.sgeventid, e.timestamp, e.ip, e.url, e.event, e.keentimestamp, e.keencreatedat, e.keenid, e.urloffsetindex, e.urloffsettype, e.sgmessageid, e.email]});
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("DATA");
sheet.getRange(sheet.getLastRow() + 1, 1, putData.length, putData[0].length).setValues(putData);
}
Looking at what you are doing here, it might be much easier to design your spreadsheet in an "append-only" format with a Zapier integration.
Zapier is able to handle SendGrid events directly, and append those events to your spreadsheet, if that is what you want.
And then you can have your "calculation columns" on a separate Sheet in the spreadsheet.
Just an idea.

Google Apps Script admin SDK speed

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
}
}