How do you track automated data inputs in Google Sheets over time? - google-apps-script

I have a google sheet that runs a report on data that is automatically updated. What I would like to do is compare the new inputs with the old inputs to determine if the changes were positive or negative. How would you go about automating a sheet to track these changes?
Changes happen monthly
there would be a score 1-100; 100 being the best
would like to store this data over time for a historical view
Any advice would surely be appreciated
The numbers in each criteria change every month producing a score at the end of the table called Current Score
This score is then pulled into the historical tab as the "Current Score"
What I would like to see happen is that the Current score be saved every month and processed with a percentage change month over month
So I would need a function that stores a copy of the results before they change, processes a new score, and then calculates the difference between the two. Example here is the Dec score (stored values) compared to the most recent score.
Here is a link to the working example
https://docs.google.com/spreadsheets/d/1ImbRhWqGjvIx2CFRKapZ2wmxC9qpSKxxCbHr5tPOBOs/edit#gid=0

Solution
You can automate this process by using Google Apps Script. Open the script editor by clicking on Tools > Script Editor. It is based on JavaScript and allows you to create, access and modify Google Sheets files with a service called Spreadsheet Service.
In addition, you can use Time-driven triggers to run the script automatically once a month. To set it up, click Triggers in the left bar, then Add Trigger and select Time-driven in Select event source. You can now specify the month timer and the exact day and hour you want the script to run. However, I recommend that you do some testing before setting up the trigger to check that you get the desired results. You can test the code by clicking Run in the Editor.
Explanation of the code
There are three functions in the code. The main function is called updateScores and it does what you described in the question. It takes the current score, stores it in a new column and calculates the difference from the last month. Try this function and if you like the result, you can put the trigger in the main function. This way, the trigger calls main which its only responsibility is to call the other two functions. The first is updateScores, which I have already explained, and the second is clearScores, which clears all the values of Reports so you don't have to do it manually and you can start writing the new values for the new month.
I have added some comments so you can understand what each line does.
var lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('report').getLastRow()
function updateScores() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Historical')
var currentValues = ss.getRange('B2:B'+lr).getDisplayValues() // get current score
ss.insertColumnsAfter(2,2) // insert two new columns (current score and percent difference)
ss.getRange('D2:D'+lr).setValues(currentValues) // paste stored score
ss.getRange('C2:C'+lr).setFormula('=if(D2=0,"N/A",B2/D2-1)') // apply formula for last stored scores
ss.getRange('E2:E'+lr).setFormula('=if(F2=0,"N/A",D2/F2-1)') // correct formula reference
ss.getRange('E2:E'+lr).copyFormatToRange(ss,3,3,2,lr) // copy format percent
ss.getRange('F2:F'+lr).copyFormatToRange(ss,4,4,2,lr) // copy format scores
var month = new Date().toString().split(' ')[1] // get current month
ss.getRange('D1').setValue(month + ' score') // write current month on last stored scores
var diff = ss.getRange('E1').getDisplayValue() // get diff symbol
ss.getRange('C1').setValue(diff) // write diff
}
function clearScores(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('report')
ss.getRange('B2:G'+lr).clear()
}
function main(){
updateScores()
clearScores()
}

Related

Adding Date+Time without 12AM moving to the next day

I am using the attached to automate a time entry to go along with the date for a calendar import. The entries don't have times, and the staff will not enter them try as I might. I need to automate them to simplify the entry procedure.
The issue I am facing is that the Calendar API needs the data to be in DATE/TIME format. To do this I need to use the =DATE+TIME formula. When I do so and the time reaches 12:00AM, the dates thereafter change to the following day.
Essentially I need to either override the logic that makes it move into the next day after midnight appears, or I need to tell either the function in column B-C that it can never roll to midnight. I am trying to think of perhaps a way that I can tell the function to reset the time if the date in column A changes to a new day, and if it doesn't change to a new day go ahead and use the existing function and add 5 minutes to the time that is shown previously to it.
I am stumped, any help would be greatly appreciated.
Here is a sheet to show you the issue
Here is the formula I tried, which worked to sort out the problem but did not work with the Calendar API requirements to format at DATE/TIME. Even when using the importrange formula to move the data into a new sheet with the cells formatted as DATE/TIME it still recognizes it as TEXT as this is what the formula prescribes.
=IF(A2<>"",(CONCATENATE(TEXT(A2,"MM/DD/YYYY")&" "&TEXT(B2,"HH:MM:SS"))),"")
I need this to work in both the sheet and in the import to Calendar using the Calendar API requirements through APPScript.
If I understood correctly your question, here a suggestion with a custom Apps Script function called like a normal Google Sheet function.
Open Apps Script Editor and paste the function below
Call the function rebuildDateTime(COL1, COL2) inside your spreadsheet
Spreadsheet:
Code:
function rebuildDateTime(arg0, arg1) {
var date = new Date(arg0);
var str = arg1.toString().split(':');
date.setHours(str[0]);
date.setMinutes(str[1]);
return date;
}
Warning :
Your COL2 (which contains only the time), must be forced to TEXT FORMAT
References :
Create a custom function

How to stop formulas from updating at a certain time each day in Google Sheets?

I want Google Sheets to stop updating a formula in a cell corresponding to the current date at a certain time each day, but I am not well versed in scripting so I need some help. I have some examples of my data below.
I have a sheet "Time Spent in Statuses" that automatically updates every hour (with a google sheets extension). New 'Keys' are added to the sheet when they are added in the extension, i.e Key '5' doesn't exist now, but when someone creates it in the data source linked with the extension, Key '5' and its corresponding values will be automatically added, so it updates frequently with more data.
In a separate sheet, I track the daily averages of numbers from the auto update in a sheet "Daily Averages" .
Currently in the "Daily Averages" sheet, I use a formula to calculate the average of each column in the "Time Spent in Statuses" sheet. This formula is pre-filled for days upcoming. At the end of each day, I have to go to this sheet and Copy and Paste > Values Only to record the averages for that day and stop the formula from continuing to update the next day. I use the 'Date' column and the Avg columns to show change in the daily averages over time in a dashboard.
What I'm looking for is some kind of logic that will look at the 'Date' column in the "Daily Averages" sheet and if it equals the current date and the time is 11:59 pm (or some other set time), then automatically Copy and Paste > Values Only. Or something similar so that I don't have to go in and manually paste values only every day. Does anyone have any experience doing something like this?
What you're looking for is a time-driven trigger in Apps Script. You can write a script and set it to run daily at a specific time.
There are multiple approaches that you can take when writing the script, but based on the sample that you provided, you can try the following code:
function setDailyAverage() {
let ss = SpreadsheetApp.getActiveSpreadsheet()
let avgsheet = ss.getSheetByName("Daily Averages")
//this is a string with today's date in format mm/dd/yyyy, like in your sample
let today = new Date().toLocaleDateString("en-US")
//this will do a search of today's date and return the row number
let row = avgsheet.createTextFinder(today).findNext().getRow()
let avgcell = avgsheet.getRange(row,2) //gets column 2 in the row corresponding to today
let giavgcell = avgsheet.getRange(row,3) //gets column 3 in the row corresponding to today
//This reads the values in each cell and overwrites the formula with the actual value
avgcell.setValue(avgcell.getValue())
giavgcell.setValue(giavgcell.getValue())
}
To add the script go to Extensions > Apps Script and paste it there, then on the left side go to Triggers > Add Trigger, then the trigger settings should look like this:
This would run the script every day between 11pm and midnight, and pretty much just automate the same "Copy and Paste > Values Only" process that you're following. You still would need to have the pre-filled rows for each date so keep that in mind. With the trigger you could also automate the creation of each row daily but I don't know if this would interfere with your workflow.

Google Apps Script to count number of emails received yesterday, that has certain label, then save # daily to spreadsheet

Basically what the title says, but I want to:
1) Automatically count the number of emails received to my gmail account, that has a certain label on it
2) Count once everyday, even if zero
3) And report daily to a Google Spreasheet
4) So I can make a monthly report like such:
Date / #
Date / #
Date / #
.
.
.
Total for October / #
Average per day / #
I'm sure this is piece of cake using Google Script for script gurus, but I have no clue. Please teach me!
Open a new Untitled spreadsheet and go to Tools -> open Script editor and paste the code given below.
function CountEmail()
{
var label = GmailApp.getUserLabelByName("LabelName");
var labelname = label.getName();
var mails = label.getThreads();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var date = new Date();
sheet.appendRow([labelname,date,mails.length]);
}
To run the script daily you will need to set a project trigger in the app script.For that follow the steps given below:
Resources -> Current Project's Trigger -> Add trigger -> Time driven -> Hour timer -> select the time when you want the script to run.
The total number of emails and the average emails/day can be calculated in the spreadsheet itself by just using the Sum() function.
Referencing the code Suyash Gandhi has posted (citing it here in case it gets removed so there is no confusion).
NOTE: not my code!!! Credit to Suyash Gandhi
function CountEmail()
{
var label = GmailApp.getUserLabelByName("LabelName");
var labelname = label.getName();
var mails = label.getThreads();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var date = new Date();
sheet.appendRow([labelname,date,mails.length]);
}
NOTE: not my code!!! Credit to Suyash Gandhi
See the picture below
What you see here are 2 threads, 1 email in the bottom one, 3 emails in the top one and 1 draft in the top one. That given code will return a 2 here. If you wish to count all 4 (or 5 if you want the draft) you will need to use Gmail API (see reference here).
I have a script that gets all emails (every message) for a specific time period and outputs the receive date, sender, recipient and title to a spreadsheet. This is the code that actually fetches the emails. The rest of the code is mostly creating files, generating the query string and reset the script if it runs too close to 6 minutes.
queriedMessages =
Gmail.Users.Messages.list(userInfo.mail,
{
'q': queryString,
'pageToken': execProperties.nextPageId
});
userInfo.mail is the email address you are fetching the emails from. This is simply written like this because the script can be run with any account
queryString is a string that is used to search for emails and is exactly the same as you use in the gmail search box. So you would have label:labelname
pageToken is a code of the page of the search (basically what is needed when you click the next page button in gmail). It is returned as part of this function so you would be able to access it from queriedMessages.nextPageToken. So if you get more than 1 page, then you will need it to access the rest of the messages.
Also, keep in mind that you are getting all the messages fitting the query, so if you do this daily, you may want to include a trigger. Also, keep in mind that functions firing from triggers ignore your timezone (known bug), but you can figure out how to create a query that works for only 1 day fairly easily. Personally I just grab +1 day on the beginning and the end and just filter those messages out.

Button to "Archive" rows based on variable

I'm pretty new to Google Sheets and I'm looking for a method/function that will allow a button I create to archive all entries in the spreadsheet to another spreadsheet.
I currently have a column that calculates the current week of the year, and another manually entered column that is for the week of the year that the entry was input. This will eventually be calculated automatically on entry of a new row, but what I'm looking to do is pull all entries from weeks prior to the current week and place those rows into another spreadsheet I dictate.
I know this is possible in Excel, but I'm not the greatest with the programming in Google Sheets, so I'm looking for some advice/help.
EDIT:
Quickly I've discovered I'm either REALLY rusty or just not great at Java. I tried the following code:
function myArchive() {
var aYear = getYear()
var sourceSheet = SpreadsheetApp.getActive()
.getSheetByName('Payroll'),
destSheet = SpreadsheetApp.openById('PayrollArchive')
.getSheetByName(aYear),
sourceRanges = ['A:A', 'B:B', 'C:C'],
targetRanges = [7, 9, 12]
.map(function (r, i) {
var sr = sourceSheet.getRange(sourceRanges[i]),
val = sr.getValues();
destSheet.getRange(1, r, val.length, val[0].length)
.setValues(val);
sr.clear()
});
}
Retrieved from here: Is there an easy format to move columns of data by script?
However, I'm trying to do a little different.
I want to copy all entries where column 1 has the week # of the previous week or before. For example, it is currently the 21st week of the year. I want it to pull all entries in the spreadsheet where column 1 is 20 or previous. Not sure how to program the current week in Java.
I want to put ALL information from the rows pulled above into a sheet named the current year in the spreadsheet named PayrollArchive. So, all of the previous weeks' worth of rows would be put in the 2015 sheet in PayrollArchive spreadsheet. I think var aYear = getYear() is the proper coding to grab the current year but is it possible to grab the value of a particular cell in the sheet to get that? I already have the current week # and date information saved in cells within the spreadsheet. The week is saved in J1, and the date is saved in D1:E1 as the last day of the current work week. For example:
=TODAY()-WEEKDAY(TODAY(),3)+6
What happens when we want to archive the last week of 2015 during the first week of 2016? How do we prevent that 2015 information going into a 2016 sheet.
Will this CREATE the sheet if it does not exist? I currently have a 2015 sheet created, but will I need to manually create a 2016 sheet or will the function create the sheet on its own?
To add a custom menu to your spreadsheet, see the documentation:
Google Documentation - Custom Menus
To insert an image, open the help menu:
Do a search on "Insert Image"
You can add a custom menu, or insert an image that is linked to a script. There probably is a function or combination of functions that you could put into the spreadsheet to archive that would get data from the first spreadsheet, then you could copy all the formulas to values. That method would use formulas, then copy the formulas to values. It can all be done in code also, but you'd need to write a script to do that. So it's possible.
See posts like these:
Google Script to copy specific columns from one sheet to another sheet
Move columns of data by script
Copy from one sheet to another sheet

How to make google spreadsheet refresh itself every 1 minute?

My google spreadsheet is using GOOGLEFINANCE('symbol','price) function to retrieve stock prices of my portfolio. Unfortunately, I have to refresh manually now. How can I make the spreadsheet refresh itself every 1 minute?
Thank you for your help.
If you're on the New Google Sheets, this is all you need to do, according to the docs:
change your recalculation setting to "On change and every minute" in your spreadsheet at File > Spreadsheet settings.
This will make the entire sheet update itself every minute, on the server side, regardless of whether you have the spreadsheet up in your browser or not.
If you're on the old Google Sheets, you'll want to add a cell with this formula to achieve the same functionality:
=GoogleClock()
EDIT to include old and new Google Sheets and change to =GoogleClock().
If you are only looking for a refresh rate for the GOOGLEFINANCE function, keep in mind that data delays can be up to 20 minutes (per Google Finance Disclaimer).
Single-symbol refresh rate (using GoogleClock)
Here is a modified version of the refresh action, taking the data delay into consideration, to save on unproductive refresh cycles.
=GoogleClock(GOOGLEFINANCE(symbol,"datadelay"))
For example, with:
SYMBOL: GOOG
DATA DELAY: 15 (minutes)
then
=GoogleClock(GOOGLEFINANCE("GOOG","datadelay"))
Results in a dynamic data-based refresh rate of:
=GoogleClock(15)
Multi-symbol refresh rate (using GoogleClock)
If your sheet contains a number of rows of symbols, you could add a datadelay column for each symbol and use the lowest value, for example:
=GoogleClock(MIN(dataDelayValuesNamedRange))
Where dataDelayValuesNamedRange is the absolute reference or named reference of the range of cells that contain the data delay values for each symbol (assuming these values are different).
Without GoogleClock()
The GoogleClock() function was removed in 2014 and replaced with settings setup for refreshing sheets. At present, I have confirmed that replacement settings is only on available in Sheets from when accessed from a desktop browser, not the mobile app (I'm using Google's mobile Sheets app updated 2016-03-14).
(This part of the answer is based on, and portions copied from, Google Docs Help)
To change how often "some" Google Sheets functions update:
Open a spreadsheet. Click File > Spreadsheet settings.
In the RECALCULATION section, choose a setting from the drop-down menu.
Setting options are:
On change
On change and every minute
On change and every hour
Click SAVE SETTINGS.
NOTE External data functions recalculate at the following intervals:
ImportRange: 30 minutes
ImportHtml, ImportFeed, ImportData, ImportXml: 1 hour
GoogleFinance: 2 minutes
The references in earlier sections to the display and use of the datadelay attribute still apply, as well as the concepts for more efficient coding of sheets.
On a positive note, the new refresh option continues to be refreshed by Google servers regardless of whether you have the sheet loaded or not. That's a positive for shared sheets for sure; even more so for Google Apps Scripts (GAS), where GAS is used in workflow code or referenced data is used as a trigger for an event.
[*] in my understanding so far (I am currently testing this)
GOOGLEFINANCE can have a 20 minutes delay, so refreshing every minute would not really help.
Instead of GOOGLEFINANCE you can use different source. I'm using RealTime stock prices(I tried a couple but this is the easiest by-far to implement. They have API that return JSON { Name: CurrentPrice }
Here's a little script you can use in Google Sheets(Tools->Script Editor)
function GetStocksPrice() {
var url = 'https://financialmodelingprep.com/api/v3/stock/real-time-
price/AVP,BAC,CHK,CY,GE,GPRO,HIMX,IMGN,MFG,NIO,NMR,SSSS,UCTT,UMC,ZNGA';
var response = UrlFetchApp.fetch(url);
// convert json string to json object
var jsonSignal = JSON.parse(response);
// define an array of all the object keys
var headerRow = Object.keys(jsonSignal);
// define an array of all the object values
var values = headerRow.map(function(key){ return jsonSignal[key]});
var data = values[0];
// get sheet by ID -
// you can get the sheet unqiue ID from the your current sheet url
var jsonSheet = SpreadsheetApp.openById("Your Sheet UniqueID");
//var name = jsonSheet.getName();
var sheet = jsonSheet.getSheetByName('Sheet1');
// the column to put the data in -> Y
var letter = "F";
// start from line
var index = 4;
data.forEach(function( row, index2 ) {
var keys = Object.keys(row);
var value2 = row[keys[1]];
// set value loction
var cellXY = letter + index;
sheet.getRange(cellXY).setValue(value2);
index = index + 1;
});
}
Now you need to add a trigger that will execute every minute.
Go to Project Triggers -> click on the Watch icon next to the Save icon
Add Trigger
In -> Choose which function to run -> GetStocksPrice
In -> Select event source -> Time-driven
In -> Select type of time based trigger -> Minutes timer
In -> Select minute interval -> Every minute
And your set :)
I had a similar problem with crypto updates. A kludgy hack that gets around this is to include a '+ now() - now()' stunt at the end of the cell formula, with the setting as above to recalculate every minute. This worked for my price updates, but, definitely an ugly hack.
use now() in any cell. then use that cell as a "dummy" parameter in a function.
when now() changes every minute the formula recalculates.
example:
someFunction(a1,b1,c1) * (cell with now() / cell with now())