I've been asked to look at the creation of a report that will pull time entry data for a list of projects. Give the project name/id, retrieve a sum of all time spent on it, and a sum of the hours from the past week.
But I can't see where in the new API anything that will give me the time entries, at least, the retrieval process is not in the Basecamp documentation.
I've been looking at this page, which describes the API:
https://github.com/basecamp/bcx-api
Turns out that you can get it from their old API:
https://mystuff.basecamphq.com/projects/#project_id#/time_entries.xml
This will give you the time entries as XML, which is a pain. It also paginates the results. In the header that you get back are two fields, that tell you how many records are there total, and how many pages:
var pageCount = headers["X-Pages"];
var recCount = headers["X-Records"];
You can process the records that come back as follows:
function processRecords(response)
{
var respObj =
{
totalHours: 0,
thisWeekHours: 0
}
var doc = null;
if (response.getContentText)
{
doc = Xml.parse(response.getContentText(), true);
}
else if (response.getElements)
{
doc = response;
}
else
{
var name = typeof response;
if (response.constructor) name = response.constructor.name;
throw new Exception("Incompatible type: " + name);
}
var root = doc.getElement();
var records = root.getElements("time-entry");
if (records.length > 0)
{
for (i = 0;i < records.length; i++)
{
var hours = Number(records[i].hours.getText());
var recordDate = records[i].date.getText();
if (recordDate >= previousSunday && recordDate <= previousSaturday)
{
respObj.thisWeekHours = respObj.thisWeekHours + hours;
}
respObj.totalHours = respObj.totalHours + hours;
}
}
return respObj;
}
What we have seen so far is there is no easy way to get actual time spent on any project. You can do something simple like time from the day project is created till its closed/deleted.
The bet way we see doing this is looking at each todo-list and then to-dos under it. Find out the cumulative time spent on each to-do under each todo-list for that project and that will be the actual (nearest possible accurate time) time spent on the project.
You can use the API to get this todo level details and that will do the trick.
If someone has better option let us know as well :) We are doing the above as of today.
Related
I am trying to create a pop up to warn a user they must update a part in inventory if the part date is more than 90 days old. The date on the sheet (Cell Q5) is autofilled from another sheet, but that shouldn't matter. The value for the cell on the spreadsheet is 9/2/2021. I've tried many things, but currently I am getting the value for Q5 showing up as NaN .
function CheckInvDate() {
var ss = SpreadsheetApp.getActive().getId();
var partsrange = Sheets.Spreadsheets.Values.get(ss, "BOM!A5:Q5");
var currentDate = new Date();
var parthist = new Date();
parthist.setDate(currentDate.getDate() -90);
for (var i = 0; i < partsrange.values.length; i++){
var name = partsrange.values [i][1]
var partdate = partsrange.values [i][16]
var parthisttime = new Date(parthist).getTime();
var partdatetime = new Date(partdate).getTime();
Logger.log("History " + parthisttime)
Logger.log("Current " + partdatetime)
SpreadsheetApp.flush()
// if (parthist > partdate == "TRUE") {
// SpreadsheetApp.getUi().alert('The price on '+ name + ' is out of date. Please update price and try again.')
// }
}
}
My last log was
[22-07-06 11:50:55:851 EDT] History 1649346655850
[22-07-06 11:50:55:853 EDT] Current NaN
I've seen a number of responses on Stack Overflow, but I can't understand them. They seem to refer to variables that I don't see in code, or commands I haven't seen before and I'm not sure if they are in date.
Try this:
function CheckInvDate() {
const ss = SpreadsheetApp.getActive();
const vs = Sheets.Spreadsheets.Values.get(ss.getId(), "BOM!A5:Q5").values;
let d = new Date();
d.setDate(d.getDate() - 90)
const dv = d.valueOf();
const oldthan5 = vs.map(r => {
if (new Date(r[16]).valueOf() < dv) {
return r;
}
}).filter(e => e);
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(`<textarea rows="12" cols="100">${JSON.stringify(oldthan5)}</textarea>`).setWidth(1000), "Older Than 90 Days");
}
This outputs a dialog with the rows older than 90 days
I went to try this on my script again after lunch, and for whatever reason I am no longer getting the NaN value. I made one change on the if statement to fix the logic, and now it is working correctly. Not sure what I did, but apparently the coding gods were unhappy with me before.
The only part I changed was
if (parthisttime > partdatetime) {
SpreadsheetApp.getUi().alert('The price on '+ name + ' is out of date. Please update price and try again.')
}
I have created an Apps Script to compile data and save the results to a new Google Sheet.
The code gets the current date with new Date() and uses that for the query and to name the new sheet it creates.
Here is the relevant part of the code:
function exportPayroll(setDate){
var date = new Date();
var newWeek = Utilities.formatDate(_getSunday(date),"GMT", "MM/dd/yyyy");
for (var company in companies){
getPayroll(companies[company],newWeek);
}
}
function _getSunday(d) {
d = new Date(d);
var day = d.getDay(),
diff = d.getDate() - day + (day == 0 ? -7:0); // adjust when day is sunday
return new Date(d.setDate(diff));
}
If I run exportPayroll manually, I get the exact results that I expect. So, I setup this trigger to automate the process:
When the trigger runs, the date value is 12/31/1969 instead of today.
Why does it act different with the trigger? Checking the execution transcript, I don't see any error messages.
Is there a better way to get today's date via a trigger?
Since you just wan't to get the Date of the previous sunday. Try running it this way.
function getLastSunday() {
var d = new Date();
var day = d.getDay();
var diff = d.getDate() - day + (day == 0 ? -7:0); // adjust when day is sunday
return new Date(d.setDate(diff));
}
function exportPayroll(){
var newWeek = Utilities.formatDate(getLastSunday(),"GMT", "MM/dd/yyyy");
for (var company in companies){
getPayroll(companies[company],newWeek);
}
}
I love Google Forms I can play with them for hours. I have spent days trying to solve this one, searching for an answer. It is very much over my head. I have seen similar questions but none that seemed to have helped me get to an answer. We have a café where I work and I created a pre-order form on Google Forms. That was the easy part. The Café can only accept pre-orders up to 10:30am. I want the form to open at 7am and close at 10:30am everyday to stop people pre ordering when the café isn't able to deal with their order. I used the very helpful tutorial from http://labnol.org/?p=20707 to start me off I have added and messed it up and managed to get back to the below which is currently how it looks. It doesn't work and I can't get my head around it. At one point I managed to turn it off but I couldn't turn it back on!! I'm finding it very frustrating and any help in solving this would be amazing. To me it seems very simple as it just needs to turn on and off at a certain time every day. I don't know! Please help me someone?
FORM_OPEN_DATE = "7:00";
FORM_CLOSE_DATE = "10:30";
RESPONSE_COUNT = "";
/* Initialize the form, setup time based triggers */
function Initialize() {
deleteTriggers_();
if ((FORM_OPEN_DATE !== "7:00") &&
((new Date()).getTime("7:00") < parseDate_(FORM_OPEN_DATE).getTime ("7:00"))) {
closeForm("10:30");
ScriptApp.newTrigger("openForm")
.timeBased("7:00")
.at(parseDate_(FORM_OPEN_DATE))
.create(); }
if (FORM_CLOSE_DATE !== "10:30") {
ScriptApp.newTrigger("closeForm")
.timeBased("10:30")
.at(parseDate_(FORM_CLOSE_DATE))
.create(); }
if (RESPONSE_COUNT !== "") {
ScriptApp.newTrigger("checkLimit")
.forForm(FormApp.getActiveForm())
.onFormSubmit()
.create(); } }
/* Delete all existing Script Triggers */
function deleteTriggers_() {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
}
/* Allow Google Form to Accept Responses */
function openForm() {
var form = FormApp.getActiveForm();
form.setAcceptingResponses(true);
informUser_("Your Google Form is now accepting responses");
}
/* Close the Google Form, Stop Accepting Reponses */
function closeForm() {
var form = FormApp.getActiveForm();
form.setAcceptingResponses(false);
deleteTriggers_();
informUser_("Your Google Form is no longer accepting responses");
}
/* If Total # of Form Responses >= Limit, Close Form */
function checkLimit() {
if (FormApp.getActiveForm().getResponses().length >= RESPONSE_COUNT ) {
closeForm();
}
}
/* Parse the Date for creating Time-Based Triggers */
function parseDate_(d) {
return new Date(d.substr(0,4), d.substr(5,2)-1,
d.substr(8,2), d.substr(11,2), d.substr(14,2));
}
I don't think you can use .timebased('7:00'); And it is good to check that you don't have a trigger before you try creating a new one so I like to do this. You can only specify that you want a trigger at a certain hour like say 7. The trigger will be randomly selected somewhere between 7 and 8. So you really can't pick 10:30 either. It has to be either 10 or 11. If you want more precision you may have to trigger your daily triggers early and then count some 5 minute triggers to get you closer to the mark. You'll have to wait to see where the daily triggers are placed in the hour first. Once they're set they don't change.
I've actually played around with the daily timers in a log by creating new ones until I get one that close enough to my desired time and then I turn the others off and keep that one. You have to be patient. As long as you id the trigger by the function name in the log you can change the function and keep the timer going.
Oh and I generally created the log file with drive notepad and then open it up whenever I want to view the log.
function formsOnOff()
{
if(!isTrigger('openForm'))
{
ScriptApp.newTrigger('openForm').timeBased().atHour(7).create()
}
if(!isTrigger('closeForm'))
{
ScriptApp.newTrigger('closeForm').timeBased().atHour(11)
}
}
function isTrigger(funcName)
{
var r=false;
if(funcName)
{
var allTriggers=ScriptApp.getProjectTriggers();
var allHandlers=[];
for(var i=0;i<allTriggers.length;i++)
{
allHandlers.push(allTriggers[i].getHandlerFunction());
}
if(allHandlers.indexOf(funcName)>-1)
{
r=true;
}
}
return r;
}
I sometimes run a log entry on my timers so that I can figure out exactly when they're happening.
function logEntry(entry,file)
{
var file = (typeof(file) != 'undefined')?file:'eventlog.txt';
var entry = (typeof(entry) != 'undefined')?entry:'No entry string provided.';
if(entry)
{
var ts = Utilities.formatDate(new Date(), "GMT-6", "yyyy-MM-dd' 'hh:mm:ss a");
var s = ts + ' - ' + entry + '\n';
myUtilities.saveFile(s, file, true);//this is part of a library that I created. But any save file function will do as long as your appending.
}
}
This is my utilities save file function. You have to provide defaultfilename and datafolderid.
function saveFile(datstr,filename,append)
{
var append = (typeof(append) !== 'undefined')? append : false;
var filename = (typeof(filename) !== 'undefined')? filename : DefaultFileName;
var datstr = (typeof(datstr) !== 'undefined')? datstr : '';
var folderID = (typeof(folderID) !== 'undefined')? folderID : DataFolderID;
var fldr = DriveApp.getFolderById(folderID);
var file = fldr.getFilesByName(filename);
var targetFound = false;
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
if(append)
{
datstr = fi.getBlob().getDataAsString() + datstr;
}
targetFound = true;
fi.setContent(datstr);
}
}
if(!targetFound)
{
var create = fldr.createFile(filename, datstr);
if(create)
{
targetFound = true;
}
}
return targetFound;
}
Here are my spreadsheet responses from a form: https://docs.google.com/spreadsheets/d/1a9H2HqAwl29IY6-aCvCKs12Xb3vDcZHCOoNugx81PTA/edit#gid=1939572907
The form data generates in the "raw data" tab of the above spreadsheet. However, I'd like to automatically rearrange the form responses in a different format on the "teacher list" tab of the spreadsheet on form submissions. We are trying to keep track of how often we visit a teacher's room and so want all of the timestamps to appear next to the teacher's name.
I do not know if I should be using formulas or a script to get the job done.
To show you our end goal, I have two form submissions that I have typed into the cells where'd we like them to appear on the "teacher list" tab.
Any suggestions or resources to help me accomplish this would be very much appreciated!
This should give you a good start. And, I have removed the merging of the cells in G column in teacher list tab.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw Data');
var data = sheet.getDataRange().getValues();
var formatSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Teacher List');
var formatData = formatSheet.getDataRange().getValues();
var name = data[sheet.getLastRow()-1][2];
var flag = 0, index;
for(var i=1; i<formatData.length; i++)
{
if(name == formatData[i][0])
{
flag = 1;
index = i;
break;
}
}
if(flag == 1)
{
for(var i=1; i<=5; i++)
{
if(formatData[index][i] == "")
{
formatSheet.getRange(index+1, i+1).setValue(data[sheet.getLastRow()-1][0]);
formatSheet.getRange(index+1, 7).setValue(formatData[index][6].concat('; '+data[sheet.getLastRow()-1][3]));
break;
}
}
}
}
But is there more than 5 visits possible? Is first column of teacher list tab is going to remian same throughout? Do you want to add new row if no match is found for 'Teacher or PLC Observed' from Raw Data with first column of Teacher List tab?
If answer to these questions is positive, you need to tweak a code little bit, try it. I'll help if you're stuck.
Edit: Please set the appscript trigger as: From form -> onSubmit.
I have emails in my inbox and ones that get archived throughout the day. Every night I want to create a script to automatically unstar them for the next day. I created this script but it doesn't seem to work. The Google docs don't seem to be much help in the way of syntax.
Here is the code I was using. Will this code access the archive as well?
function processInbox() {
var threads = GmailApp.getInboxThreads();
for (var i = 0; i < threads.length; i++) {
var firstThread = GmailApp.getInboxThreads(0,1)[0];
var message = firstThread.getMessages()[0];
GmailApp.unstarMessage(message);
}
};
You are working only on the first thread in your inbox.
GmailApp.getInboxThreads(0,1)[0];
You needed to put your 'i' variable in that line in order to iterate on the messages.
Try something like that:
// first limit the script for the top 50 emails (or a bit more) but don't run on ALL of them -it's not efficient.
var threads = GmailApp.getInboxThreads(0, 50);
for (var i = 0; i < threads.length; i++) {
var message = threads[i].getMessages()[0];
GmailApp.unstarMessage(message);
}
Good luck.