Google sheet script to search and retrieve Gmail data - google-apps-script

I've started a script that matches an email address in Col D with Gmail and writes the last message date in Col E. I'd like that script to repeat row by row - now it only writes to Row 2 (newbie question - sorry).
I'd also like to search for data in addition to "from". I'd like to also search "to" and other variables.
Lastly, I'd like this to have a trigger that is time-based (daily or weekly) and I'm still getting my head wrapped around triggers, so any help is really appreciated.
function myFunction() {
// Get the value in the cell D2 (which will have the email to check the latest interaction)
var ss =
SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('D2').getValue();
// Search the date of the last message of the search from the email in cell D1 and log it
var latestEmail = GmailApp.search('from:"'+ss+'"')[0].getLastMessageDate();
Logger.log(latestEmail);
// Print the date on the cell to the right
SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('E2').setValue(latestEmail);
}

I'd like that script to repeat row by row - now it only writes to Row 2
You will need to iterate ("loop") through your sheet's rows.
Try this:
function myFunction() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1')
const addresses = sheet.getRange(2, 4, sheet.getLastRow()-1).getValues().flat().filter(Boolean)
for (let [index, address] of addresses.entries()) {
const latestEmail = GmailApp.search(`from: ${address}`)[0].getLastMessageDate()
sheet.getRange(index+2, 5).setValue(latestEmail);
}
}
Commented:
function myFunction() {
// Your sheet.
const sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1')
// All cells in Column D (Column 4).
const addresses = sheet.getRange(2, 4, sheet.getLastRow()-1).getValues().flat().filter(Boolean)
// For each address in Column D... (While keeping track of the index)
for (let [index, address] of addresses.entries()) {
// Get the last message date from this address...
const latestEmail = GmailApp.search(`from: ${address}`)[0].getLastMessageDate()
// And set the adjacent cell to that date.
sheet.getRange(index+2, 5).setValue(latestEmail);
// "Index+2" because index starts at 0 and we started our range at row 2.
}
}
I'd also like to search for data in addition to "from". I'd like to also search "to" and other variables.
That can all be done using GmailApp.search()
Lastly, I'd like this to have a trigger that is time-based (daily or weekly) and I'm still getting my head wrapped around triggers, so any help is really appreciated.
Couldn't be easier, see here:
Managing triggers manually
In your Script Editor, click the Triggers icon and then click the Add Trigger button.
You will be looking to choose this function, from event source: "Time-driven", and select when you would like it to run.

Related

Google sheets - Search for a date in a range and return value underneath?

I'm trying to search for a specific date and use the whole sheet as a range, and return the value underneath (the checkbox), which is either True (if ticked) or False (if not ticked). The numbers on the sheet are actually dates, i've just formatted them to look this way.
So for example, let's say that today i want to search for the date 2022-03-30 (the marked number on the picture below) and retrieve the value underneath to see if the checkbox is ticked or not, how would I go about this?
Picture below:
Google sheet url that you guys can view/edit: https://docs.google.com/spreadsheets/d/1poFFukwrPZFLynt1SmDaNcELZlbPFfEibOWAwRNsafI/edit?usp=sharing
If anyone has any idea on how to make this work, maybe we can insert the formula inside "Sheet1"?
Thanks alot in advance, any tips are appreciated.
I believe your goal is as follows.
In your showing sample Spreadsheet, you want to know whether the checkbox is checked or unchecked.
From let's say that today i want to search for the date 2022-03-30 (the marked number on the picture below) and retrieve the value underneath to see if the checkbox is ticked or not,, in this case, you want to check whether the checkbox of "T12" is checked.
You want to achieve this using Google Apps Script.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please set the sheet name, and save the script.
function myFunction() {
const sheetName = "Sheet1"; // Please set your sheet name.
const oneMonth = { cols: 7, rows: 14 }; // Number of columns and rows of 1 month.
const calendar = { cols: 4, rows: 3 }; // Order of months in your calendar.
const spaceCol = 1; // I found an empty column between each month.
const today = new Date(); // This is today date.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const months = [...Array(calendar.rows)].flatMap((_, i) => [...Array(calendar.cols)].map((_, j) => sheet.getRange(i * oneMonth.rows + 1, j * oneMonth.cols + 1 + j * spaceCol, oneMonth.rows, oneMonth.cols)));
const res = months[today.getMonth()].createTextFinder(today.getDate()).findNext().offset(1, 0).isChecked();
console.log(res) // You can see "true" or "false" in the log.
}
When this script is run, in the case of today (2022-07-05), the checkbox of "S20" is retrieved.
In order to search the day in a month, I used TextFinder.
Note:
This sample script is for your provided Spreadsheet. So, when you change your Spreadsheet, this script might not be able to be used. Please be careful about this.
Reference:
Class TextFinder

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

Is there a way of returning a value in a cell when a code is entered?

I am looking to setup a product sheet that contains barcodes linked to a product name. I have all my products entered in column A and I will enter the products corresponding barcode in column B
In another sheet which I use as an Inventory, I would then like to be able to scan the barcode into column a but return the product name.
I have found a workaround with a formula but this interrupts some of the Google apps script I already run in the sheet so would be looking for a Google apps script
The code that I am looking for would read something like;
onEdit() when barcode is scanned/entered into sheet 1 column a, it looks at sheet 2 column b and returns the corresponding value in that row from sheet 2 column a into sheet 1 column a.
I trust this makes sense, I'm only asking if someone knows of a quick answer for this please?
You will need to use Installable Triggers to achieve your goal, because a Simple Trigger onEdit will not work because of its Restrictions that don't allow to set values into sheets as you would want.
Script executions and API requests do not cause triggers to run. For
example, calling Range.setValue() to edit a cell does not cause the
spreadsheet's onEdit trigger to run.
Taking into consideration you already solved the part to pass the barcode values to your sheet, then use this code:
function getItemName(e) {
// Get the two sheets
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var sheet1 = sheets[0];
var sheet2 = sheets[1];
// Get the row and col where the event was triggered
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
// If it was in col 1 (A) and row 1, then do something
if(col === 1 && row === 1){
// Get the items at the second sheet
var items = sheet2.getRange(2, 1, sheet2.getLastRow() - 1, 2).getValues();
// iterate over them
for(var i = 0; i < items.length ; i++){
// if the code is found, set it on the first sheet and exit the loop
if(items[i][0] === range.getValues()[0][0]){
sheet1.getRange("B1").setValues([[items[i][1]]]);
return
}
}
// if no item found, print this message
sheet1.getRange("B1").setValues([["No item found"]]);
}
}
Now, for setting up the installable trigger, do the following:
1) Go to your Apps Script project
2) Click Edit->Current project's triggers
3) Click "+ Add Trigger"
4) Select :
Choose which function to run -> getItemName
Select event source-> From spreadsheet
Select event type -> On edit
5) Click Save

Google Apps Scripts Send automated email with trigger (not repetitively)

I'm attempting to set up automated emails through google sheets using scripts and a trigger.
How do I define only new additions to the spreadsheet should trigger an email? The spreadsheet is constantly added to.
function sendloggeremails() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow()
for (var i = 72; i <= lr; i++) {
var currentEmail = ss.getRange(i, 7).getValue();
var currentClassTitle = ss.getRange(i, 3).getValue();
MailApp.sendEmail(currentEmail, "complaint: customer number " + currentClassTitle , "Please check through the log as you have a new assigned to you");
}
}
var i = 72 plainly because this is the last row, I don't want to have to manually change this constantly. Added triggers but at the moment I still need to go into the code to change var i.
Any chance anyone can help with this?
Sending Emails only Once with a Loop
You could use something like this. To get started you would want to put something in the sent column so that old lines would not resend their emails and you'd still maintain a record of past emails. I suggested putting the string "SENT". But the test just ask the question is that column empty so anything will work.
Obviously, I haven't seen you spreadsheet so I don't know where to put the sentColumn so you can change the var sentColumn = 75 to anything you wish and all of the other places it's used will change accordingly.
function sendloggeremails() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getActiveSheet();//You should probably change this to getSheetByName() and then put in the correct name of the sheet
var sentColumn=75;//If this column is not empty then don't send emails. If you send any email then also put "SENT" into this column so you wont resend it next time you run the loop.
sh.getRange(1,sentColumn).setValue("Sent");//I'm assuming a header row and I'm putting a Header Title there you can pick any open column you want
var rg=sh.getDataRange();//This gets all of the sheets data into one one range
var vA=rg.getValues();//This gets all of the values in above range in a two dimension object we often refer to as a two dimensional array.
//If you have header row you can start at i=1
for(var i=1;i<vA.length; i++) {//This will loop over all of the rows on the sheet.
if(!vA[i][sentColumn-1]){//Heres the test to see if theres something in sentColumn. If sentColumn is empty meaning truthy is false then it send the email
var currentEmail=vA[i][6];//this replaces the getValue from column7 *1
var currentClassTitle=vA[i][2];//this replaces the getValue from column3 *1
MailApp.sendEmail(currentEmail, "complaint: customer number " + currentClassTitle , "Please check through the log as you have a new assigned to you");
sh.getRange(i+1,sentColumn).setValue("SENT");//After sending email we put something. In this case "SENT" into the sentColumn so that next through the loop we won send another email because its truthy will be true.
}
}
}
//*1 Array indices are 1 less that column numbers because arrays start counting from zero.
If you wish we could also delete the rows as we send them. All you have to do is keep track of how many rows you have deleted each time the loop is run with say a variable like var n=0 to start with and then the row number that gets deleted will be i-n+1. And right after the deletion you increment n by one.
You can setup the trigger using this function.
function setupEmailTrigger() {
if(ScriptApp.getProjectTriggers().indexOf('sendloggeremails')==-1){
ScriptApp.newTrigger('sendloggeremails').timeBased().everyDays(1).atHour(17).create();
}
}
It checks to see if the trigger already exists and if the trigger already exists it does nothing.

google sheets form function not updating with data input from the script

I'm having some trouble getting a function in a google sheet to calculate using values inserted by a script.
I have a google script that is adding data to a sheet based on user-inputted data from a form that the script has created. So, in the form, a user inputs their name, selects a product and some options, and the script adds this to a sheet named 'Client Data Sheet'.
I then have a different sheet which is supposed to do the math to calculate the price. My script copies all the functions from a hidden template row into the next-available row, so, for example, cell D7 contains ='Client Data Sheet'!A4, D8 contains ='Client Data Sheet'!B4 etc... These all display the correct values.
The problem is the price calculation function, in that same calculation sheet, which has a rather complex function that a previous coder wrote. This function should calculate the price of the product and options based on the data in the same sheet. It does so without calling a script, just pulling data from cells in other sheets, running some if() checks to decide whether or not to add extra costs to the total price, and adding it all up.
Problem is, it doesn't update based on the new data. It just shows 0, as though the other cells were empty, even though they now contain data updated by pulling data from another sheet which was edited by a script. If I go in to edit the function and just press enter, it re-calculates correctly, so I basically just need the function to re-evaluate based on the new data that is in the cells it's dependent on.
My theory is that it's not updating the function since I didn't directly edit the cells it's dependent on. I could try to change the awkward, huge function this other coder wrote so it pulls from the spreadsheet my script edits, rather than from cells that copy in that data, but that seems like a workaround, and is unsatisfying.
TL;DR: a function isn't updating based on data that changes in cells that copy the data from other cells which are filled by a script. Anybody have any advice for how to get the function to update?
EDIT: Ok, so if I make sure that the function that isn't updating pulls at least some data from cells that the script updates, it works. It seems that it doesn't recognize that the cell once removed has updated as well. It would be better, though if it was able to pull from the cells that pull their data from the cells the script updates. This would let other users of the sheet change the products if a customer requested a change later on without having to edit my hidden sheets that the data is pulled from.
Code:
This copies a template row in my spreadsheet that contains the math functions:
function new_client(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Liquidation');
if(ss.getName()=="Data" || ss.getName().indexOf("Liq")==0){
var last_row = ss.getLastRow();
var last_col = ss.getLastColumn();
ss.insertRowsAfter(last_row, 1);
var template_row = 4;
var copy_range = ss.getRange(template_row, 1, 1, last_col);
var paste_range = ss.getRange(last_row+1, 1, 1, last_col);
copy_range.copyTo(paste_range, {contentsOnly:false});
paste_range.getCell(1,1).setValue("NO");
SpreadsheetApp.flush();
}
}
This copies data from the form into a different spreadsheet, which the sheet edited above pulls data from:
var clientSheet = SpreadsheetApp.getActive().getSheetByName('Client Data Sheet');
clientSheet.insertRowsAfter(clientSheet.getMaxRows(), 1)
var lastRow = clientSheet.getLastRow() + 1;
var lastColumn = clientSheet.getLastColumn();
var destRow = clientSheet.getRange(lastRow, 1, 1, lastColumn);
var column = 1;
for (var key in user) {
if (user.hasOwnProperty(key)) {
destRow.getCell(1, column).setValue(user[key]);
column++;
}
}
So, for example, after this code is run, the Client Data Sheet contains a cell, B4, which now contains the name of the chosen product, "foo". The Liquidation sheet has a cell let's call it A5 that contains the function ='Client Data Sheet'!B4, as well as another cell which has the price calculation function: =if(A5="foo", 100, 0)
When the script above inserts the values from the form, the cell B4 in Client Sheet and the cell A5 in the Liquidation sheet will contain the right value, but the cell with the calculation function =if(B4="foo", 100, 0) will not update.