AppScript: Google Calendar Remove Events - javascript

I have the following code that uploads my events from Google Sheet to Google Calendar. This code searches the rows that need to be synced to the calendar based on value in column E (status) x[4]. Events that are marked as Pending Schedule or Pending Removal will be synced to the calendar and then update the respective rows in the sheet with Scheduled or Removed.
This code runs just fine in terms of scheduling events. But when it comes to removing events it only works if the first row of the data is marked to be removed, but not on other rows. It gives me TypeError: Cannot read property 'deleteEvent' of undefined and when I investigated the deleteEvent object shows as CalendarEvent.
Seems like the way I have written the if() functions is somehow not appropriate and the else if() does not evaluate the whole data. I have tried replacing else if() with another if() but received the same error.
function calendarSync2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName('Test Sheet')
var settingsSheet = ss.getSheetByName('Calendar Settings');
var calendarId = settingsSheet.getRange('B2').getValues();
var eventCal = CalendarApp.getCalendarById(calendarId);
var eventArray = dataSheet.getRange('A2:I').getDisplayValues();
for (x = 0; x < eventArray.length; x++) {
var event = eventArray[x];
var eventName = event[1];
var eventDate = new Date(event[0]);
var timeZone = event[5];
var startTime = new Date(`${event[0]} ${event[2]} ${timeZone}`); ///Used Object literals of `` combined with ${}
var endTime = new Date(`${event[0]} ${event[3]} ${timeZone}`);
var status = event[4]; // Used in the following comparison expression instead of filter
if (status === 'Pending Schedule') { /// === undertakes strict comparison
var exisEvents = eventCal.getEvents(startTime, endTime, {search: eventName}); // prevents creation of duplicates
if (exisEvents.length == 0) {
Logger.log(`timed ${eventName} scheduled for: ${startTime} till ${endTime}`);
eventCal.createEvent(eventName, startTime, endTime);
eventArray[x][4] = "Scheduled"; // Updates the status
}
} else if (status === 'Pending Removal') {
Logger.log(`${eventName} at ${eventDate} REMOVED`)
var returnedEvent = eventCal.getEvents(startTime, endTime, {search: eventName});
var toRemove = returnedEvent[x];
Logger.log(`returnedEvent is: ${returnedEvent}`);
toRemove.deleteEvent();
eventArray[x][4] = "Removed";
}
}
dataSheet.getRange('A2:I').setValues(eventArray); // Overwrite the source data with the modified array
}

So with a little bit of digging I figured I need to create another loop for removing events. so the following code:
else if (status === 'Pending Removal') {
var returnedEvent = eventCal.getEvents(startTime, endTime, {search: eventName});
var toRemove = returnedEvent[x];
toRemove.deleteEvent();
eventArray[x][4] = "Removed";
}
Should be changed to:
else if (status === 'Pending Removal') {
var returnedEvents = eventCal.getEventsForDay(eventDate,
{search: eventName});
for (i = 0; i < returnedEvents.length; i++) {
var toRemove = returnedEvents[i];
toRemove.deleteEvent();
eventArray[x][4] = 'Removed';
}
}

Related

Google Sheets Scripts - run scripts as administrator / owner

I have Google Sheet, name TEST https://docs.google.com/spreadsheets/d/1HsRwknyZBmZZ9nibDfNpOwqkVsFGThDyrTwspV-5_4U/edit?usp=sharing
Sheet: Arkusz 1
Column A: all people can edit
Column B: only owner can edit
Library (for everyone): https://script.google.com/macros/s/AKfycbzpnEMhIG-0dMp54q3W4UxoT71-lSdfF7Qxf7rq_j6gJMNIxuCS/exec
A user cannot add a row because it is blocked by column B, which belongs only to the admin.
How can I create macro, which allow user to add new rows?
I have three scripts:
function insertRow() {
var ss = SpreadsheetApp.getActive()
var sheetName = ss.getActiveSheet().getName()
var row = ss.getActiveRange().getRow()
var numRows = Browser.inputBox('Insert Rows', 'Enter the number of rows to insert', Browser.Buttons.OK);
Logger.log(numRows)
var url ="https://script.google.com/macros/s/AKfycbzpnEMhIG-0dMp54q3W4UxoT71-lSdfF7Qxf7rq_j6gJMNIxuCS/exec"
var queryString = "?sheetName="+sheetName+"&rowNo="+row+"&noOfRows="+numRows
url = url + queryString
Logger.log(url)
var request = UrlFetchApp.fetch(url)
if (request != 'Success')
Browser.msgBox(request)
}
Second:
function doGet(e) {
var param = e.queryString
var parameters = param.split("&")
// This just checks only 3 parameters are present else gives a invalid link
if (param != null && parameters.length == 3){
param = e.parameter
var name = param.sheetName
var row = Number(param.rowNo)
var numOfRows = Number(param.noOfRows)
} else{
return ContentService.createTextOutput("Invalid query")
}
try{
var ss = SpreadsheetApp.openById("https://docs.google.com/spreadsheets/d/1HsRwknyZBmZZ9nibDfNpOwqkVsFGThDyrTwspV-5_4U")
var sheet = ss.getSheetByName(name)
sheet.insertRowsAfter(row, numOfRows);
var source_range = sheet.getRange(row,1,1,sheet.getLastColumn());
var target_range = sheet.getRange(row+1,1,numOfRows);
source_range.copyTo(target_range);
}
catch (err){
return ContentService.createTextOutput("error: "+err)
}
return ContentService.createTextOutput("Success")
}
And after clicked function insertRow and filled number of rows I have doPost(e) information.
Could you help me?
On the solution you provided below, I see that the issue is in mainScript
function mainScript(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
// assign the sheet to a variable and use it below instead of spreadsheet
var sheet = spreadsheet.getSheetByName('ZNC')
sheet.getRange('A2').activate()
sheet.insertRowsBefore(sheet.getActiveRange().getRow(), 1);
}
Hmm, I created solution, but I think there's a bug somewhere, because it doesn't add the line, even though everything is correct and the script is published as public.
function ZNCWiersz() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('ZNC'), true);
const activeSheet = SpreadsheetApp.getActiveSheet().getSheetName();
const url = ScriptApp.getService().getUrl();
UrlFetchApp.fetch(`${url}?sheetName=${activeSheet}`, {
headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() },
});
// DriveApp.getFiles() // This is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive.readonly". This scope is used for the access token.
}
// When runScript() is run, this function is run.
const doGet = (e) => ContentService.createTextOutput(mainScript(e));
// This script is run by Web Apps.
function mainScript(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
spreadsheet.getSheetByName('ZNC')
spreadsheet.getRange('A2').activate()
spreadsheet.insertRowsBefore(spreadsheet.getActiveRange().getRow(), 1);
}

If GoogleJsonResponseException: then skip and move to next row

I have a working script. need to improvise to have no manual interruption. We have multiple Profiles in Analytics, sometimes we lose access and sometimes we have. So when i run the Script, If we lost access to 1 of 60 profiles, i have to delete that entry manually then rerun the script.
What i want is, If there is below error, Then skip and continue with next row
"GoogleJsonResponseException: API call to analytics.data.ga.get failed with error: User does not have sufficient permissions for this profile."
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = doc2.getSheetByName("Dashboard");
for(var i=52;i<65;i++){
var viewId = dashboard.getRange(i,13).getValue(); // Your Google Analytics view ID
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = {'segment': 'gaid::-5'};
var result = Analytics.Data.Ga.get(viewId, metric, option);
var metric = result.totalsForAllResults['ga:metric'];
var metric2 = result.totalsForAllResults['ga:metric2'];
var metric3 = result.totalsForAllResults['ga:metric3'];
var doc = SpreadsheetApp.getActiveSpreadsheet(); // Current document
var sheet = doc.getActiveSheet(); // Current sheet
sheet.getRange(i,14,1,1).setValue(metric);
sheet.getRange(i,15,1,1).setValue(metric2);
sheet.getRange(i,16,1,1).setValue(metric3);
} }
try it this way:
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var sh = doc2.getSheetByName("Dashboard");
var sheet = doc2.getActiveSheet(); // Current sheet
const vs = sh.getRange(52, 13, 13).getValues();
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = { 'segment': 'gaid::-5' };
for (var i = 0; i < vs.length; i++) {
var viewId = vs[i][0]; // Your Google Analytics view ID
try {
var result = Analytics.Data.Ga.get(viewId, metric, option);
}
catch(e){
continue;
}
if (result) {
sheet.getRange(i + 52, 14, 1, 3).setValues([[result.totalsForAllResults['ga:metric'], result.totalsForAllResults['ga:metric2'], result.totalsForAllResults['ga:metric3']]]);
}
}
}
Without the benefit of working data some of this may not be correct but using setValues and getValues should speed it up considerably and the try catch blocks should help with not getting result consistently. Also you want to avoid making unnecessary declarations in loops.
I might understand the question incorrectly (if so, please clarify) but it sounds to me like you just need to add...
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = doc2.getSheetByName("Dashboard");
for(var i=52;i<65;i++){
try { //...this line and...
var viewId = dashboard.getRange(i,13).getValue(); // Your Google Analytics view ID
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = {'segment': 'gaid::-5'};
var result = Analytics.Data.Ga.get(viewId, metric, option);
var metric = result.totalsForAllResults['ga:metric'];
var metric2 = result.totalsForAllResults['ga:metric2'];
var metric3 = result.totalsForAllResults['ga:metric3'];
var doc = SpreadsheetApp.getActiveSpreadsheet(); // Current document
var sheet = doc.getActiveSheet(); // Current sheet
sheet.getRange(i,14,1,1).setValue(metric);
sheet.getRange(i,15,1,1).setValue(metric2);
sheet.getRange(i,16,1,1).setValue(metric3);
} catch(e) { //...this part
console.log(e); //optional, catch(e){} is perfectly valid as well, or any code you might want to execute on error
}
} }

How to get active user email with installabled onEdit trigger? [duplicate]

This question already has an answer here:
onEdit trigger doesn't catch current user
(1 answer)
Closed 3 months ago.
I have a Google spreadsheet with some data. I wrote script to track changes of some specific columns.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var menuItems = [
{name: 'Turn on', functionName: 'createSpreadsheetEditTrigger'}
];
ss.addMenu('Tracker', menuItems);
}
function changeTrack(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ui = SpreadsheetApp.getUi();
var ws = ss.getActiveSheet();
const headerRow = 4;
const editBodyCols = [2, 3, 4, 5];
const fResultCol = 6;
var range = ws.getActiveRange();
var row = range.getRow();
var col = range.getColumn();
let target1 = ws.getRange(row, fResultCol);
let target2 = ws.getRange(row, fResultCol + 1)
let activeUser = getCurrentUserEmail();
if(row > headerRow && editBodyCols.some(x => x === col) === true){
if(target1.getValue() !== ""){
target2.setValue(result(ss, ws, row, activeUser)[1]);
} else {
target1.setValue(result(ss, ws, row, activeUser)[0])
target2.setValue(result(ss, ws, row, activeUser)[1])
}
}
}
function createSpreadsheetEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('changeTrack')
.forSpreadsheet(ss).onEdit()
.create();
}
function date(){
return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss");
}
function result(ss, ws, row, activeUser) {
const ssName = ss.getName();
let data = `Создал ${activeUser} ${date()}`;
let exp = `Файл ${ssName}, Лист ${ws.getName()}, изменил ${activeUser}, строка № ${row}, ${date()}`;
let adds = [];
adds.push([data],[exp]);
return adds;
}
function getCurrentUserEmail()
{
var email=Session.getActiveUser().getEmail();
return email;
}
My problem is to get active user's email. This script can get it but not all the time. Seems like random success. It means sometimes I can get expected value, sometimes not. I don't understand what is it depends from.
Where I'm wrong and how to fix it?
From the documentation on Session.getActiveUser():
Gets information about the current user. If security policies do not allow access to the user's identity, User.getEmail() returns a blank string. The circumstances in which the email address is available vary: for example, the user's email address is not available in any context that allows a script to run without that user's authorization, like a simple onOpen(e) or onEdit(e) trigger, a custom function in Google Sheets, or a web app deployed to "execute as me" (that is, authorized by the developer instead of the user).
So this seems pretty expected and there is no hard workaround you can make to retrieve the users mail. You should maybe just ask for it and see if they be willingly want to give it to you.
Although if you are the developer or the users are inside your organization this restrictions may be ignored:
However, these restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
Based on the comment by b-frid.
The flow:
create a custom menu and tell each user to run the function twice. First time for authorization, and the second time to actually run the code.
this will install the same trigger with the user's privileges and let the onEdit trigger get the email of an active user (author of the trigger)
video-instruction:
https://www.loom.com/share/30b11e4d012447f7a1efdd9a7eac4fca
gist:
https://gist.github.com/Max-Makhrov/e3e89e7fe0c6e86b68a4be1262e53629
Code:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('😎📬 run me 2x times')
.addItem('please let script to see your email', 'install')
.addToUi();
}
// function test() {
// var e = {
// range: SpreadsheetApp.getActive().getSheetByName('test_onEdit').getRange('B2')
// }
// edit_(e)
// }
function edit_(e) {
var targetcol = 1;
if (e.range.getSheet().getName() === 'Sheet1') {
var user = Session.getActiveUser().getEmail();
if (user !== '') {
var col = e.range.getColumn();
var rows = e.range.getHeight();
var ratgetrange = e.range.offset(
0,
targetcol - col,
rows,
1);
ratgetrange.setValue(user);
}
}
}
function install() {
setOnEditTrigger_(
SpreadsheetApp.getActive().getId(),
'edit_');
}
/**
* create onEdit trigger
*
* #param {string} spreadsheetId
* #param {string} functionName
*
*/
function setOnEditTrigger_(spreadsheetId, functionName) {
console.log('OnEdit trigger ' + functionName +
' for new file' +
spreadsheetId);
var trigger;
if (existsOnEditTrigger_(functionName, spreadsheetId)) {
console.log('stopped execution. Trigger exists.');
return;
}
trigger = ScriptApp
.newTrigger(functionName)
.forSpreadsheet(spreadsheetId)
.onEdit()
.create();
console.log('Created new trigger!')
return trigger.getUniqueId();
}
/**
* check if onEdit trigger exists
*
* #param {string} spreadsheetId
* #param {string} functionName
*
*/
function existsOnEditTrigger_(functionName, spreadsheetId) {
var triggers = ScriptApp.getProjectTriggers();
var trigger = {};
for (var i = 0; i < triggers.length; i++) {
trigger = triggers[i];
if (
trigger.getHandlerFunction() === functionName &&
trigger.getTriggerSourceId() === spreadsheetId &&
trigger.getEventType().toString() === 'ON_EDIT') return true;
}
return false;
}
Original comment:
The key is separate triggers for each user. Then I simply added code
to execute the update only if the Session.getActiveUser().getEmail()
call does not come back blank. Of course, because each's user's
trigger will run, the function will execute x times where x = the
number of users (i.e. triggers), but with the check for a blank return
value the logic only runs once (and so no overwrites). A bit clumsy
indeed, and perhaps not practical if you have more than a handful of
users, but workable in my case.

Function works as expected when called from function using XMLHttpRequest, but fails when called from a function using EventSource. Why is this?

I'm working on creating a basic messenger using Javascript. I have three functions in a separate js file called loadMessage, messageListener, and displayMessage.
The function loadMessage makes a call to my database for all existing messages, and then calls displayMessages to construct some divs which I use to show the messages I got from the server. These divs are created to appear under each other, with the bottom div being the newly created one showing the latest message.
Once all the messages have been created loadMessage then calls messageListener. This function 'listens' for any new messages which might appear on the database. If any appear then messageListener calls displayMessage. I expect this to create a new div at the bottom of my other divs as before, however when it calls displayMessage the behaviour is completely different than when loadMessage calls displayMessage.
Specifically, it does not create a new div but instead just changes the text in an existing div which appears anywhere within the newly created divs (for example, the div which displays the first message or one somewhere in the middle).
My HTML and PHP files all behave as expected, so I think my issue is somewhere in these three functions.
How can I fix this to behave as expected?
Code:
// Loads chat messages history and listens for upcoming ones.
function loadMessages(userID, contactID) {
contactIDGlobal = contactID;
//load existing messages
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var param = "userID="+userID+"&contactID="+contactID+"&date="+date;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST","Interface-getMessage.php?", true);
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xmlhttp.send(param);
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
//retrives a string of all past messages
var messageString = xmlhttp.responseText;
//parse string to get messages.
var parseMessageString = messageString.split('-');
for (var i = 0; parseMessageString[i] !== null && parseMessageString[i] !== ''; i = i+5){
var contactID = parseMessageString[i];
var senderID = parseMessageString[i+1];
var message = parseMessageString[i+2];
var time = parseMessageString[i+3];
var mID = parseMessageString[i+4];
displayMessage(userID, senderID, contactID, message, date, time, mID);
}
}
};
//listen for new messages
messageListener(userID, contactID);
}
function messageListener(userID, contactID){
if(typeof(EventSource)!=="underfined") {
var newMessage = new EventSource("testerfile.php?userID="+userID+"&contactID="+contactID);
newMessage.onmessage = function(event) {
var newMessageData = event.data;
var parseNewMessage = newMessageData.split('-');
//sender ID may be different to the userID due to the way that messages are stored on the server. Received messages have a different sender.
var senderID = parseNewMessage[0];
var contactID = parseNewMessage[1];
var message = parseNewMessage[2];
var date = parseNewMessage[3];
var time = parseNewMessage[4];
var messageID = parseNewMessage[5];
console.log(event.data);
displayMessage(userID, senderID, contactID, message, date, time, messageID);
};
}else {
document.getElementById("messages").innerHTML = "Your browser does not support this";
}
}
// Displays a Message in the UI.
function displayMessage(userID, senderID, contactID, nMessage, date, time, id){
var messageListElement = document.getElementById('messages');
var messageInputElement = document.getElementById('message');
// If an element for this message already exists, then get it
var id = id;
var div = document.getElementById(id);
// If an element for that message does not exists yet we create it.
if (!div) {
var container = document.createElement('div');
if (userID == senderID){
container.innerHTML = SENDER_MESSAGE_TEMPLATE;
}else{
container.innerHTML = MESSAGE_TEMPLATE;
}
div = container.firstChild;
div.setAttribute('id', id);
for (var i = 0; i < messageListElement.children.length; i++) {
var child = messageListElement.children[i];
}
messageListElement.insertBefore(div, child);
}
var messageElement = div.querySelector('.message');
messageElement.textContent = nMessage;
// Replace all line breaks by <br>.
messageElement.innerHTML = messageElement.innerHTML.replace(/\n/g, '<br>');
}
// Template for messages.
var SENDER_MESSAGE_TEMPLATE =
'<div class="sender_message-container">' +
'<div class="message"></div>' +
'</div>';
var MESSAGE_TEMPLATE =
'<div class="message-container">' +
'<div class="message"></div>' +
'</div>';
The problem was coming from the date being returned as y-m-d, and the parser using "-". This mean I was creating a time var which was the month of my date, and the message ID as the day. I made the alteration below to fix this...
var newMessageData = event.data;
var parseNewMessage = newMessageData.split('-');
//sender ID may be different to the userID due to the way that messages are stored on the server. Received messages have a different sender.
var senderID = parseNewMessage[0];
var contactID = parseNewMessage[1];
var message = parseNewMessage[2];
var date = parseNewMessage[3]+"-"+parseNewMessage[4]+"-"+parseNewMessage[5];
var time = parseNewMessage[6];
var messageID = parseNewMessage[7];

How to concatenate values from a pop-up window choice with values from a database?

I am developing a project with the Kendo UI Framework, using more specically the Scheduler widget and I have the current issue:
On my database I have two tables one called Events and the other one called TypeOfEvents. Each type of event has got a specific color, a specific title plus defined values for startHour and endHour fields.
When the pop-up window to create an event is called, I can choose on two kendoMultiSelect the correspondent user and the type of event.
I can also choose the startDate and endDate. The default behavior of a Scheduler widget has got two datetimepickers also, however, I don't want that option on my pop-up window because the events will have defined hours that an user can't change.
My idea would be the following one:
Once I click save after choosing a specific event on my MultiSelectList, there would be some way to concatenate the startHour and endHour values I have defined in my database with the startDate and endHour field that I choosed on the pop-up window.
Right now, all my events startDate/endDate fields are saved on my DB with this format: 2015-03-01 00:00:00.000
I would like to substitute all those zeros with the values I defined in advance in my startHour/endHour fields of my TypeOfEvents table.
Here's my current CREATE script:
create: function (createEvent) {
var typeOfEventID = $("#selectEvent").val();
var usernameID = $("#selectUsername").val();
var dataStartTemp = $("#dataStart").val();
var dataEndTemp = $("#dataEnd").val();
var note = $("#note").val();
var res = $("#customViewScheduler").data("kendoScheduler");
var res1 = res.resources[1].dataSource.data();
var dataStart = convertToJSONDate(dataStartTemp);
var dataEnd = convertToJSONDate(dataEndTemp);
var changeSet = [];
var id = 0;
usernameID.forEach(function (userID) {
typeOfEventID.forEach(function (eventID) {
var titletemp = $.grep(res1, function (elem) {
if (elem.TypeOfEventID == eventID) {
return true;
}
})
if (titletemp.length > 0) {
note = titletemp[0].title;
}
var entityChange = {};
entityChange.Id = id;
entityChange.Entity = {
'__type': "Events:#BlahBlahWeb",
'UsernameID': userID,
'TypeOfEventID': eventID,
'startDate': dataStart,
'endDate': dataEnd,
'Title': note
};
entityChange.Operation = 2;
changeSet.push(entityChange);
id++
})
})
var changesetPayload = JSON.stringify({
"changeSet": changeSet
});
//Create jQuery ajax request
var Params = {}
Params.type = "POST";
Params.url = "./../Services/BlahBlahWeb-BlahDomainService.svc/JSON/SubmitChanges";
Params.dataType = "json";
Params.data = changesetPayload;
Params.contentType = "application/json";
Params.error = function (httpRequest, textStatus, errorThrown) {
//SendErrorByEmail(errorThrown, httpRequest.responseText)
}
Params.success = function (data) {
//createEvent.success(data);
var scheduler = $("#customViewScheduler").data("kendoScheduler");
var elem = tratanewelem(data.SubmitChangesResult[0].Entity)
scheduler.dataSource.read();
}
//Make the ajax request
$.ajax(Params);
},
Any idea of how can I accomplish that?

Categories