I have a script that runs in a google sheet that parses emails and creates new lines in the sheet. This is used to create a log file from periodically emailed log updates. This works very well.
Currently, I have a variable that is used to determine which emails are ingested based on the month (0=January, etc.)
That variable has to be adjusted every month and then I have to create a new monthly sheet (tab in the main) and do a bunch of sorting and moving emails in gmail.
I'd like to set this up so it automatically puts the January emails in a sheet for January and the February emails in a sheet for February.
I thought about cascading if elseif statements, but that got too unwieldy fast.
I thought about iterating using a for loop through an array holding all emails, but that seems convoluted too.
Any suggestions?
::EDIT::
To be clear, I'm really interested in how to parse all of the emails and send the ones from January to the January sheet (for example).
::EDIT:: Added current script
function myFunction() {
var label = GmailApp.getUserLabelByName(myLabel);
var label2 = GmailApp.getUserLabelByName(newLabel);
var threads = label.getThreads();
var data = new Array();
var newData = new Array();
// get all the email threads matching myLabel
for (var i = 0; i < threads.length; i++) {
var messages = GmailApp.getMessagesForThread(threads[i]);
// archive thread
label2.addToThread(threads[i]);
label.removeFromThread(threads[i]);
// get each individual email from the threads
for (var j = 0; j < messages.length; j++) {
var bodyText = messages[j].getPlainBody();
// split the email body into individual "paragraph" strings based on the regExp variable
while (matches = regExp.exec(bodyText)) {
var logdata = matches[1];
for (k in keys) {
logdata = logdata.replace(keys[k], "");
}
// split out each "paragraph" string into an array
var lines = logdata.split(/[\r\n]+/);
for (l in lines) {
lines[l] = lines[l].replace('*F','');
lines[l] = lines[l].trim();
}
for (l in lines) {
lines[l] = lines[l].replace(/^(\:\s)/, "");
}
// Turn the first element in the array into a date element, format it, and put it back
lines[0] = Utilities.formatDate(new Date(lines[0]), "America/Phoenix", "M/d/yy HH:mm:ss");
// Put the array to a new item in the data array for further processing
if (curMonth == (new Date(lines[0]).getMonth())) {
data.push(lines);
}
}
}
}
// Compare the information in the data array to oldData information in the sheet
if (data.length) {
var oldData = s.getRange(range).getValues();
for (h in oldData) {
oldData[h][0] = Utilities.formatDate(new Date(oldData[h][0]), "America/Phoenix", "M/d/yy HH:mm:ss");
}
for (i in data) {
var row = data[i];
var duplicate = false;
for (j in oldData) {
if (row.join() == oldData[j].join()) {
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
// check to write newData only if there is newData, this stops an error when newData is empty
if (newData.length) {
s.getRange(s.getLastRow() + 1, 1, newData.length, newData[0].length).setValues(newData);
}
s.getRange(range).sort(1); //sorts the sheet
}
}
Try this:
function getSheet(date) {
var ss=SpreadsheetApp.openById('SpreadsheetId');
var name=Utilities.formatDate(new Date(date), Session.getScriptTimeZone(), "MMMyyyy")
var sh=ss.getSheetByName(name);
if(!sh) {
var sh=ss.insertSheet(name);
}
return sh;
}
I ended up basing my solution on the solution that #cooper provided, but had to go a little further.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var month = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
var curMonth = new Date().getMonth(); //number of month -1 aka: January = 0
var sheetname = month[curMonth] + " " + new Date().getYear();
var s = ss.getSheetByName(sheetname);
function newMonth(){
if (!s) {
var template = ss.getSheetByName('Template').copyTo(ss);
template.setName(sheetname);
s = ss.getSheetByName(sheetname); //"reload" the sheet
s.showSheet(); //unhide the new copy since 'Template' is hidden in the spreadsheet
ss.setActiveSheet(s); //make it active
ss.moveActiveSheet(0); //move it to the first position
}
}
Related
I'm having trouble matching a string in an array. Column B2:Lastrow is defined as array which is "ID". I am trying to paste only unique entries to google sheet which aren't available in Column B2:Lastrow. Issue is..when I run the code it allows duplicates in the google sheet as well.
I was using it through count formula on the sheet but that leads to maximum code runtime error..hence I'm using the range as an array. Solves the error but not able to recognize if the string is unique.
// Code: List Gmail Label to Google Sheet and save attachment to GDrive
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('Summary');
var label = GmailApp.getUserLabelByName("Caterpiller Account");
var threads = label.getThreads();
function getEmails() {
for (var i = 0; i < threads.length; i++) {
var row = sheet.getLastRow() + 1;
var message = threads[i].getMessages()[0];
var ID = message.getId();
var fulldata = sheet.getRange('B2:B' + row).getValues();
if (fulldata.indexOf(ID) == -1) {
var messages=threads[i].getMessages();
var listID=threads[i].getPermalink();
var listdate=threads[i].getLastMessageDate();
var message = threads[i].getMessages()[0];
var attachment = message.getAttachments();
var attachmentBlob = message.getAttachments()[0].copyBlob();
var folder = DriveApp.getFolderById("1ilsecZOexqTWGfAMu5xJDx1pKh3z1US-");
// EXTRACTOR CODE:
for (var m=0; m < messages.length; m++) {
sheet.getRange(row,1).setValue(messages[m].getSubject());
sheet.getRange(row,2).setValue(ID);
sheet.getRange(row,3).setValue(listdate); // Value - Date
for (var z=0; z<attachment.length; z++) {
var file = DriveApp.getFolderById("1ilsecZOexqTWGfAMu5xJDx1pKh3z1US-").createFile(attachmentBlob);
//Pending: Weblinkview (basically get permanent url of file) / Or self developed function that gets file through description (where description is email ID)
}
row++;
}
}
}
}
Expected: Unique entries & a faster Code runtime.
Actual: I'm crap & code time is still the same.
bool IsSame(string str,char arr[100])
{
if(str.lenght!=strlen(arr))return false;
for(int i=0;i<str.lenght;i++)
{
if(str[i]!=arr[i]) return false;
}
return true;
}
I'm trying to run an IF function to match the date in the first column to "last month" and the date in the last column to "newest date" and copy and paste all of the rows matching this criteria (excluding the first and last column) to the bottom of the list.
This is the script I'm running and it isn't finding any matches when I know for a fact there are at least 100 rows matching this criteria:
function myFunction() {
var MCS = SpreadsheetApp.openById('[ID REMOVED FOR THIS Q]');
var MRB = MCS.getSheetByName('Media Rates Back');
var MRBrange = MRB.getRange(1,1,MRB.getLastRow(),1).getValues();
var dest = MRBrange.filter(String).length + 1;
var LM = new Date();
LM.setDate(1);
LM.setMonth(LM.getMonth()-1);
var LMs = Date.parse(LM);
var Datenew = MRB.getRange(MRB.getLastRow(),MRB.getLastColumn()).getValue();
var Datecol = MRB.getRange(1,6,MRB.getLastRow(),1).getValues();
var Datenews = Date.parse(Datenew);
for(var i=0; i<MRBrange.length; i++) {
if(Date.parse(MRBrange[i])==LMs && Date.parse(Datecol[i])==Datenews ) {
var NewRange = MRB.getRange(i,2,(MRB.getLastRow()-i),5);
var NewRangeV = NewRange.getValues();
var destination = MRB.getRange(MRB.getLastRow()+1,2);
Logger.log(NewRange);
NewRange.copyTo(destination);
}else{
Logger.log(i);
}
}}
Any help would be appreciated!
Rather than get the columns as separate ranges, I would get the entire range as one array, then loop over that and check the two columns.
I'm also assuming your values are formatted as dates in the Sheet, in which case you don't need to use Date.parse(), and that your actual date logic is correct.
You can try using the debugger and set a breakpoint at the IF, so you can check the values it is comparing. or put a Logger.log call to list your comparisons.
var last_month_column = 1;
var newest_date_column = MRB.getLastColumn();
var MRBrange = MRB.getRange(1,1,MRB.getLastRow(),newest_date_column).getValues();
for(var row in MRBrange) {
if(MRBrange[row][last_month_column]==LMs && Datecol[row][newest_date_column] ==Datenews ) {
/* your copy logic here */
}else{
Logger.log(i);
}
}
I think the problem may be that MRBrange is a 2d Array. So I used another loop to convert it to a 1d array.
function myFunction() {
var MCS = SpreadsheetApp.openById('[ID REMOVED FOR THIS Q]');
var MRB = MCS.getSheetByName('Media Rates Back');
var MRBrangeA = MRB.getRange(1,1,MRB.getLastRow(),1).getValues();//2d array
var MRBrange=[];
for(var i=0;i<MRBrangeA.length;i++)
{
MRBrange.push(MRBrangA[i][0]);//1d array
}
var dest = MRBrange.filter(String).length + 1;
var LM = new Date();//current day
LM.setDate(1);//first day of month
LM.setMonth(LM.getMonth()-1);//first day of last month
var LMs = Date.parse(LM);
var Datenew = MRB.getRange(MRB.getLastRow(),MRB.getLastColumn()).getValue();
var Datecol = MRB.getRange(1,6,MRB.getLastRow(),1).getValues();
var Datenews = Date.parse(Datenew);
for(var i=0; i<MRBrange.length; i++) {
if(Date.parse(MRBrange[i])==LMs && Date.parse(Datecol[i])==Datenews ) {
var NewRange = MRB.getRange(i,2,(MRB.getLastRow()-i),5);
var NewRangeV = NewRange.getValues();
var destination = MRB.getRange(MRB.getLastRow()+1,2);
Logger.log(NewRange);
NewRange.copyTo(destination);
}else{
Logger.log(i);
}
}}
I currently have a list with two columns. The first column is student name, and the second column is the number of points they have.
I imported this list from multiple spreadsheets so there were many duplicates on the names of the students. I am able to remove the duplicates, but I want to keep a tally on the total points they have. For example:
Amy 10
Bob 9
Carol 15
Amy 12
would turn into:
Amy 22
Bob 9
Carol 15
This is what I have so far:
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var data = sheet.getRange("A2:B1000").getValues();
var newData = new Array();
var k = 0
var finallist = []
for(i in data){
k++;
var row = data[i];
var duplicate = false;
for(j in newData){
if(row[0] == newData[j][0]){
duplicate = true;
var storedHour = sheet.getRange("B"+k).getValue();
var position = finallist.indexOf(row[0]);
var originalCell = sheet.getRange("B"+(position+1));
var originalHour = originalCell.getValue();
originalCell.setValue(originalHour + storedHour);
sheet.getRange(k,2).setValue("")
sheet.getRange(k,1).setValue("")
}
}
if(!duplicate){
newData.push(row);
finallist.push(row[0])
}
}
}
The problem I'm having is that we have a really large data sample and I'm afraid it may run over Google's 5 minute maximum execution time. Is there another more efficient way to achieve my goal?
Your code is running slow because Spreadsheets API methods (like getRange) are time consuming and much slower then other JavaScript code.
Here is optimized function with reduced number of such Spreadsheets API calls:
function calcNumbers()
{
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var lastRow = sheet.getLastRow();
var dataRange = sheet.getRange(2, 1, lastRow-1, 2);
var data = dataRange.getValues();
var pointsByName = {};
for (var i = 0; i < data.length; i++)
{
var row = data[i];
var curName = row[0];
var curNumber = row[1];
// empty name
if (!curName.trim())
{
continue;
}
// if name found first time, save it to object
if (!pointsByName[curName])
{
pointsByName[curName] = Number(curNumber);
}
// if duplicate, sum numbers
else
{
pointsByName[curName] += curNumber;
}
}
// prepare data for output
var outputData = Object.keys(pointsByName).map(function(name){
return [name, pointsByName[name]];
});
// clear old data
dataRange.clearContent();
// write calculated data
var newDataRange = sheet.getRange(2, 1, outputData.length, 2);
newDataRange.setValues(outputData);
}
Sorting before comparing allows looking at the next item only instead of all items for each iteration. A spillover benefit is finallist result is alphabatized. Execution time reduction significant.
function sumDups() {
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var data = sheet.getRange("A2:B" + sheet.getLastRow()).getValues().sort();
var finallist = [];
for(var i = 0; i<= data.length - 1; i++){
var hours = data[i][1];
while((i < data.length - 1) && (data[i][0] == data[i+1][0])) {
hours += data[i+1][1];
i++;
};
finallist.push([data[i][0], hours]);
};
Logger.log(finallist);
}
Edit: the simple data structure with the name being in the first column allows this to work. For anything more complex understanding and applying the methods shown in #Kos's answer is preferable
I have looked at various solutions posted i.e. parsing, substrings and splitting and none of them either produce a value or the required value.
The format received via Salesforce API is "2014-08-19T02:26:00.000+0000"
Essentially I would like a custom function that can be used within Google Sheets to convert this date/time format and take daylight saving into consideration
Thank you beforehand
I use a simple function like below :
function parseDate(string) {
var parts = string.split('T');
parts[0] = parts[0].replace(/-/g, '/');
var t = parts[1].split(':');
var refStr = new Date(new Date(parts[0])).toString();// use this to get TZ for daylight savings
var fus = Number(refStr.substr(refStr.indexOf('GMT')+4,2));
return new Date(new Date(parts[0]).setHours(+t[0]+fus,+t[1],0));
}
firstly thank you for everyone's input. By using a combination of the info provided by RobG and Serge insas I revised the script and created one that suited my needs. Please see below, any further advice would be welcome.
/*
The script first has all variables declared.
As the script runs inconjunction with an API query running off single trigger for defined sequential functions where the previous parsed date records are cleared and then re-parsed and runs with loop function for a whole column of data within specified range
*/
function parseDate() {
var source_spreadsheet = SpreadsheetApp.openById("Sheet_Id");
SpreadsheetApp.setActiveSpreadsheet(source_spreadsheet);
var sheet = source_spreadsheet.getSheetByName("Sheet_Tab");
var startRow = 2;
var numRows = 4500;
var startCol = 1;
var numCols = 7;
var dataRange = sheet.getRange(startRow, startCol, numRows, numCols)
sheet.getRange(startRow, startCol + 1, numRows, numCols - 1).clear({contentsOnly: true});
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var SFConnectDate = row[0];
var DConnected = row[1];
var SFCutoverDate = row[2];
var DInUse = row[3];
var Lat = row[5];
var Long = row[6];
if (SFConnectDate != "" && DConnected == "" && Lat != "" && Long != "") {
var parts = SFConnectDate.split('T');
parts[0] = parts[0].replace(/-/g, '/');
var Fdd = parts[0].split('/');
var AllTime = parts[1].split('.');
var Ftt = AllTime[0].split(':');
var D = new Date(Fdd[0],(Fdd[1]-1),Fdd[2] ,Ftt[0],Ftt[1],Ftt[2]);
var TZ = (D.getTimezoneOffset())/60;
var DConnected = new Date(Fdd[0],(Fdd[1]-1),Fdd[2],(Ftt[0]-TZ),Ftt[1],Ftt[2]);
sheet.getRange(startRow + i, 2).setValue(DConnected);
}
}
}
I have a Google AdWords script that is taking yesterdays cumulative spend (filtered) and inputting the value into a Google Sheet row. The logic I have in place is tested and works correctly, but when I try to replicate the logic for a different filter condition the value that is passed back for the replicated logic comes back with a value of 0. I believe the issues has to do with my .withCondition filter logic, but it looks correct to me.
Adwords Script:
function main() {
var sheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1kKPwvazsT9YOfL5swKRkjHYAdUtetetetetetet/edit#gid=0").getActiveSheet();
var emptyRow = findEmptyRow(sheet);
var yesterday = new Date(new Date()-1);
var range = sheet.getRange(emptyRow + 1, 1, 1, 10);
var row = range.getValues();
var campaignIteratorPaidSearch = AdWordsApp.campaigns().withCondition("Name NOT_IN ['Remarketing', 'GSP', 'YouTube'] ").forDateRange('YESTERDAY').get();
var campaignIteratorDisplay = AdWordsApp.campaigns().withCondition("Name IN ['Remarketing', 'GSP', 'YouTube'] ").forDateRange('YESTERDAY').get();
var totalPaidSearchCost = 0;
var totalDisplayCost = 0;
var date = new Date();
date.setDate(date.getDate() - 1);
//Paid Search Spend
while (campaignIteratorPaidSearch.hasNext()) {
var campaignStats = campaignIteratorPaidSearch.next();
var stats = campaignStats.getStatsFor('YESTERDAY');
totalPaidSearchCost += stats.getCost();
}
//Display Spend
while (campaignIteratorDisplay.hasNext()) {
var displayCampaignStats = campaignIteratorDisplay.next();
var displayStats = displayCampaignStats.getStatsFor('YESTERDAY');
totalDisplayCost += displayStats.getCost();
}
row[0][0] = date;
row[0][1] = totalPaidSearchCost;
row[0][2] = totalDisplayCost;
range.setValues(row);
}
function findEmptyRow(sheet) {
var dates = sheet.getRange(1, 1, 365, 1).getValues();
for (var emptyDate = 0; emptyDate < dates.length; emptyDate++) {
if (dates[emptyDate][0].length == 0) {
return emptyDate;
}
}
}
The campaign name is a String so you can not use NOT_IN and IN operators, you should use:
= != STARTS_WITH STARTS_WITH_IGNORE_CASE CONTAINS CONTAINS_IGNORE_CASE DOES_NOT_CONTAIN DOES_NOT_CONTAIN_IGNORE_CASE
https://developers.google.com/adwords/scripts/docs/reference/adwordsapp/adwordsapp_campaignselector#withCondition_1