Google Script: Email Alert with report - google-app-engine

I have a spreadsheet that is continually being updated. On Tuesday Afternoons, I want to receive a report via email of rows with a certain column that is blank. For example, if C1:C100 is blank, I want an email of the entire pasted row.
I saw a previous question that helped, but I still need some guidance. I am very new to writing scripts. Essentially, I want to be able to use it as a reporting tool. So a specific person does not need to open up the doc and can delegate appropriately.
Also, another script that could possibly send an email with the spreadsheet once all the rows are completed.
Google App Script to trigger email
Send Email when value changes in Google Spreadsheet
Thanks!

Not sure about your specifics, but here is a general skeleton:
function onTuesdays() {
var s = SpreadsheetApp.getActiveSheet();
var firstRow = 3; // Specify the first row that data can be in.
var column;
var rowComplete;
var rows = [];
var recipients = 'something#something.com';
var subject = 'Tuesday Afternoon Report';
var message = '';
for (var i = firstRow; i <= s.getLastRow(); i++) {
column = 1;
rowComplete = true;
do {
if (s.getRange(i, column).isBlank()) {
rows.push(i);
rowComplete = false;
}
else column++;
}
while (column <= s.getLastColumn() && rowComplete);
}
if (rows.length == 0) message += 'There were no empty cells this week.';
else {
message += 'The following rows had empty cells this week:<ul>';
for (var i = 0; i < rows.length; i++) message += '<li>' + rows[i] + '</li>';
message += '</ul>';
}
MailApp.sendEmail(recipients, subject, message);
}
I'm not sure how you want the information to be displayed, but that should give you a general start.
Hope this helps!

I think that you can use the Code writed before and add a Trigger Event that send you and Email automatically.
Read the section: https://developers.google.com/apps-script/managing_triggers_programmatically

Related

Google Sheets custom function displays "Loading..." forever in mobile app

I have written a custom function for Google Sheets in Apps Script. The goal is to have a sheet which automatically calculates who owes how much money to whom (e.g. to split a bill).
My sheet looks like this:
The first bill (Restaurant) is to be split among all 5 and the second bill is to be split among all 5 except Peter, because there is no 0 in B3.
The input for my Apps Script function will be cells B1 to F3 (thus, values AND names). The function works fine - it calculates the correct results. I open that spreadsheet via browser (sheets.google.com) AND via my phone app (Google Sheets). However, on my phone it often happens that the result cell (with the formula =calc_debt(B1:F3)) only displays "Loading ...". What's the problem?
For the sake of completeness, here is custom function's code:
function calc_debt(input) {
var credit = [0, 0, 0, 0, 0]; // credit[0] = Peter, credit[1] = Mark ...
for (var i = 1; i < input.length; i++) { // starting at i = 1 to skip the first row, which is the names!
// first: calculate how much everybody has to pay
var sum = 0;
var people = 0;
for (var j = 0; j <= 4; j++) {
if (input[i][j] !== "") {
sum += input[i][j];
people += 1;
}
}
var avg_payment = sum / people;
// second: calculate who has payed too much or too little
for (var j = 0; j <= 4; j++) {
if (input[i][j] !== "") {
credit[j] += input[i][j] - avg_payment;
}
}
}
// this function is needed later
function test_zero (value) {
return value < 0.00001;
};
var res = ""; // this variable will contain the result string, something like "Peter to Mark: 13,8 | Katy to ..."
while (!credit.every(test_zero)) {
var lowest = credit.indexOf(Math.min.apply(null, credit)); // find the person with the lowest credit balance (will be minus!)
var highest = credit.indexOf(Math.max.apply(null, credit)); // find the person with the highest credit balance (will be plus!)
var exchange = Math.min(Math.abs(credit[lowest]), Math.abs(credit[highest])); // find out by how much we can equalize these two against each other
credit[lowest] += exchange;
credit[highest] -= exchange;
res += input[0][lowest] + " to " + input[0][highest] + ": " + exchange.toFixed(2) + " | "; // input[0] = the row with the names.
}
return res;
}
I'm having a similar issue in the android app that loading a custom formula sometimes just shows 'Loading...', while in the web it always works fine. I've found a workaround to load the formulas in the android app:
Menu - > Export - > Save as - > PDF.
This will take a moment and behind the modal loading indicator you will see that the formulars eventually resolve. You can wait for the export to finish or cancel it as soon as you see your formular was resolved.
Also making the document available offline via the menu toggle could resolve the formulars.
Another thing you could do is using caching in your script. So whenever you use the web version to render more complex formulars the results are being stored and immediately loaded for the mobile app. Unfortunately, the Google cache is limited in time and does invalidate after a few hours. See here for more information:
https://developers.google.com/apps-script/reference/cache/
This two things work quite well. However, I'm searching for a better solution. Let me know if you find one.
Solved follow the solution provided here ..
Menu - > Export - > Save as - > PDF
This forces the script to run on mobile and be readable by the mobile sheet

Handling multiple and duplicate emails for Google MailApp

I am trying create a script for Google Sheets that does the following:
1) triggers on a certain date
2) compiles information from columns based on a date criteria
3) sends an email digest of multiple column values to each unique triggered email row
I've looked up several alternate examples, but I cannot grasp the syntax to handle step 3.
The data is generated from Google Forms submissions.
Submissions are added continuously. Script has a time-driven trigger.
I want it to compile all data for entries by the SAME email respondent on the triggered date AND separate out data intended for different email addresses.
I've grabbed a script and altered it to the best of my ability. (see below)
Currently, it produces the following results:
1) script triggers correctly based on date
2) compiles information from triggered columns and reports in one email to all addresses from triggered rows. (incorrect behavior)
Currently the script runs a 'for loop' to grab all triggered data for body of email and sends ALL data to ALL emails. I understand why it does this but I can't wrap my head around how to execute 'step 3' above.'
I think I need to create an object for the email data and run another "for loop" to isolate the reference email.
I'm not sure where to go from here, or how to grab the column value to isolate the required email.
Spreadsheet layout:
Column A: Timestamp
Column B: Recorded Email
Column C - F: Personalized Info
Column G: Requested Followup Date
Column H: Days Remaining until Followup Date
Data set starts on Row 2
Email is triggered when Column H reports value of 0.
Thank you in advance for any and all assistance
Current Script:
function checkReminder() {
// get the spreadsheet object
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// set the first sheet as active
SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
// fetch this sheet
var sheet = spreadsheet.getActiveSheet();
// figure out what the last row is
var lastRow = sheet.getLastRow();
// the rows are indexed starting at 1, and the first row
// is the headers, so start with row 2
var startRow = 2
// grab column 8 (the 'days left' column)
var range = sheet.getRange(2,8,lastRow-startRow+1,1);
var numRows = range.getNumRows();
var reminder_date = range.getDisplayValues();
//grab DOB column
var DOB = sheet.getRange(2,4,lastRow-startRow+1,1)
var DOBrange = DOB.getDisplayValues();
//grab ph# column
var phnum = sheet.getRange(2,5,lastRow-startRow+1,1)
var phnumrange = phnum.getDisplayValues();
//Admin Date of 1st shot
var Admindate = sheet.getRange(2,6,lastRow-startRow+1,1)
var Admin = Admindate.getDisplayValues();
// Now, grab the patient name column
range = sheet.getRange(2,3, lastRow-startRow+1,1);
var patient_info_values = range.getValues();
//grab email
var Emailrows = sheet.getRange(2,2,lastRow-startRow+1,1);
var Emaillist = Emailrows.getValues();
var sendit = 0;
var msg = "";
var greeting = "This email is to remind your team to followup with the following individuals. \n \n";
// Loop over the days left values
for (var i = 0; i <= numRows - 1; i++) {
var days_left = reminder_date[i][0];
if(days_left == 0) {
// if it's equal to 0, do something with the data.
var Adminreport = Admin[i][0];
var patient_name = patient_info_values[i][0];
var DOB_name = DOBrange[i][0];
var phnumgrab = phnumrange[i][0];
msg = msg + " "+patient_name+" DOB: "+DOB_name+" Phone#: "+phnumgrab+" First dose given "+Adminreport+" \n\n";
sendit++;
}
}
if(sendit)
{
MailApp.sendEmail(Emaillist, "Followup Reminder", greeting + msg);
}
};

google apps script: setValues from array, taken from html form onSubmit in google sheets

In Google Sheets, I have a sidebar using html, with a form which runs processForm(this) upon submission. The form was created based on the headings in the sheet, which is why I am using the headings to retrieve the values from the form. The code seems to work fine until I try to use setValues(). There is no error, but nothing seems to happen at that line. Please let me know what I might be doing wrong. Thanks.
function processForm(formObject) {
var headers = getHeaders();
var newRow = [];
for (var i = 0; i < headers.length; i++) {
newRow.push(formObject["" + headers[i]]); // TODO: convert objects to appropriate formats
}
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(parseInt(formObject.row)+1, 1, 1, headers.length)
Logger.log(JSON.stringify(newRow)); // example output: ["John","Smith","male","6615554109","","example_email#yahoo.com"]
range.setValues(newRow); // values not getting set
}
Change last line:
range.setValues([newRow]);
(thanks for the solution, Serge insas!)

I have a spreadsheet with a hundred groups and can't get-for/ loop to work

I have the following to pull users in a gmail group. Problem is, I have a spreadsheet with like a hundred groups and can't get my traditional For/ loop to work. I need to loop through the spreadsheet, add the new group email and run again
function getGroupMembers() {
try {
var groupEmail = 'GroupAddress#gmail.com';
var group = GroupsApp.getGroupByEmail(groupEmail);
var users = group.getUsers();
for (var u = 0; u < users.length; u++){
// do something witty with the list of users
Logger.log(users[u].getEmail());
}
} catch(err) {
Logger.log(err.lineNumber + ' - ' + err);
}
}

Audit of what records a given user can see in SalesForce.com

I am trying to determine a way to audit which records a given user can see by;
Object Type
Record Type
Count of records
Ideally would also be able to see which fields for each object/record type the user can see.
We will need to repeat this often and for different users and in different orgs, so would like to avoid manually determining this.
My first thought was to create an app using the partner WSDL, but would like to ask if there are any easier approaches or perhaps existing solutions.
Thanks all
I think that you can follow the documentation to solve it, using a query similar to this one:
SELECT RecordId
FROM UserRecordAccess
WHERE UserId = [single ID]
AND RecordId = [single ID] //or Record IN [list of IDs]
AND HasReadAccess = true
The following query returns the records for which a queried user has
read access to.
In addition, you should add limit 1 and get from record metadata the object type,record type, and so on.
I ended up using the below (C# using the Partner WSDL) to get an idea of what kinds of objects the user had visibility into.
Just a quick'n'dirty utility for my own use (read - not prod code);
var service = new SforceService();
var result = service.login("UserName", "Password");
service.Url = result.serverUrl;
service.SessionHeaderValue = new SessionHeader { sessionId = result.sessionId };
var queryResult = service.describeGlobal();
int total = queryResult.sobjects.Count();
int batcheSize = 100;
var batches = Math.Ceiling(total / (double)batcheSize);
using (var output = new StreamWriter(#"C:\test\sfdcAccess.txt", false))
{
for (int batch = 0; batch < batches; batch++)
{
var toQuery =
queryResult.sobjects.Skip(batch * batcheSize).Take(batcheSize).Select(x => x.name).ToArray();
var batchResult = service.describeSObjects(toQuery);
foreach (var x in batchResult)
{
if (!x.queryable)
{
Console.WriteLine("{0} is not queryable", x.name);
continue;
}
var test = service.query(string.Format("SELECT Id FROM {0} limit 100", x.name));
if(test == null || test.records == null)
{
Console.WriteLine("{0}:null records", x.name);
continue;
}
foreach (var record in test.records)
{
output.WriteLine("{0}\t{1}",x.name, record.Id);
}
Console.WriteLine("{0}:\t{1} records(0)", x.name, test.size);
}
}
output.Flush();
}

Resources