0

I hope you can assist me here.

disclaimer: I'm new to Google Sheets and JavaScript, but have many years of programming experience.

I've got a pretty simple Custom Function in a sheet (Contained) that works absolutely fine if the Sheet is open in the browser. It basically takes in 5 arrays from the sheet and outputs 5 arrays from the cell from which it was called.

The sheet provides data to several other sheets that calculate from that data and then in turn share that to a further 'public' sheet that is then shared to a WordPress site.

ThisData -> CalculationSheet -> PublicViewSheet -> WordPress site

The problem kicks in after an hour or two after I close the Google Sheet in my browser, and I notice in the WordPress site that the data that was nicely displaying the leaderboard now outputs no athletes or their scores.

If I then:

  1. merely open ThisData sheet and do nothing else, the calculated data is there and then all the athletes scores appears in the WordPress site 5 mins later due to caching.
  2. instead open CalculationSheet or PublicViewSheet the 'QUERY( IMPORTRANGE(' that retrieves the data from ThisData retrieves no data (merely just the static headers).

I thought it might be the Google Sheets Custom Function caching problem and I've tried the trick of setting a cell to a Date/Time (see 'updateLastEditDate' in code below) and then referencing that cell through the score calculation (CalculateScores) as a dummy variable, and then setting up a trigger every hour to change the date which forces a recalculation... but I'm still getting the same disappearing results issues.

I've got a logger running to take a look at the output every time the CustomFunction runs, and when reviewing the logs, I can see that the output data is correct, but it seems that it's not getting written to the sheet. Or it might be getting written, but it's not showing further down the line ???

I guess I could rather change the CustomFunction to a function that is triggered onEdit as well as once a day (via triggers) and directly read the data from the sheet and then write it back... but a CustomFunction seems a neater solution, and allows me to move data ranges, add columns, etc without having to edit and hardwire the CalculateScores function.

I'm just scratching my head on why it's causing the above behavior... any ideas would be most welcome.

See code below: (for all it's worth... as it produces the expected output)

/** @OnlyCurrentDoc */

/**
 * Calculates Precision Rifle scores..
 *
 * @param {lMatchNames}.
 * @return {array} a full array of calculated scores.
 * @customfunction
 */

function CalculateScores(lMatchNames, lAthleteIDs, lAthleteDivisions, lAthleteScores, lTieBreakScores, lTieBreakTimes, lDate ){

  if(lMatchNames==undefined || lAthleteIDs==undefined || lAthleteDivisions==undefined || lAthleteScores==undefined || lTieBreakScores==undefined || lTieBreakTimes==undefined){
    console.log("Setting null parameters");
    var lMatchNames = [[]];
    var lAthleteIDs = [[]];
    var lAthleteDivisions = [[]];
    var lAthleteScores = [[]];
    var lTieBreakScores = [[]];
    var lTieBreakTimes = [[]];
  }else{
  }

console.log(lNumMatches);

  var lNumMatches = lMatchNames.length;

  var lMatchData = [];
  for (var i = 1; i < lNumMatches; i++) {
    //Find Match Object
    var lMatchName = lMatchNames[i][0];
    var lAthleteID = lAthleteIDs[i][0];
    var lAthleteDivision = lAthleteDivisions[i][0];
    var lAthleteScore = lAthleteScores[i][0];
    var lMatchObject = lMatchData.find(item => item.name == lMatchName);
    if (lMatchObject === undefined) {
      // Setup new match object in Array
      lMatchObject = {
        name:lMatchName,
        combinedScores:[],
        tieBreakScores:[],
        };
      lMatchData.push(lMatchObject);
    }
    // Add info to Match Object
        if(lAthleteID!="0"){
          addAndSort(lMatchObject.combinedScores, lAthleteScore);
          var lTieBreakScore = (lAthleteScore * 100000000) + (lTieBreakScores[i][0] * 100000) - lTieBreakTimes[i][0]
          addAndSort(lMatchObject.tieBreakScores, lTieBreakScore);
          let lKeys = Object.keys(lMatchObject);
          if(lKeys.indexOf(lAthleteDivision)==-1){
            lMatchObject[lAthleteDivision]=[];
            var lAthleteDivisionTieBreak = lAthleteDivision + "TieBreak";
            lMatchObject[lAthleteDivisionTieBreak]=[];
          }
          addAndSort(lMatchObject[lAthleteDivision], lAthleteScore);
          addAndSort(lMatchObject[lAthleteDivisionTieBreak], lTieBreakScore);
        }
  }

//Loop back through the data and calculate outputs
var lOutputArray = [];
lOutputArray.push(["Overall%", "OverallRank", "Division%", "DivisionRank", "RankTotal"]);
for (var i = 1; i < lNumMatches; i++) {
  var lMatchName = lMatchNames[i][0];
  if (lMatchName != ""){
    var lAthleteID = lAthleteIDs[i][0];
    var lAthleteDivision = lAthleteDivisions[i][0];
    var lAthleteDivisionTieBreak = lAthleteDivision + "TieBreak";
    var lAthleteScore = lAthleteScores[i][0];
    var lTieBreakScore = (lAthleteScore * 100000000) + (lTieBreakScores[i][0] * 100000) - lTieBreakTimes[i][0]
    if(lAthleteID=="0"){
      var lOverallPercentage = "";
      var lOverallRank = "";
      var lDivisionPercentage = "";
      var lDivisionRank = "";
      var lTieBreakScore = "";
    }else{
      var lMatchObject = lMatchData.find(item => item.name == lMatchName);
      var lOverallPercentage = lAthleteScore / lMatchObject.combinedScores[lMatchObject.combinedScores.length-1];
      lOverallPercentage = Math.round(lOverallPercentage * 100000) / 100000;
      var lOverallRank = lMatchObject.tieBreakScores.length - lMatchObject.tieBreakScores.lastIndexOf(lTieBreakScore);
      var lDivisionPercentage = lAthleteScore / lMatchObject[lAthleteDivision][lMatchObject[lAthleteDivision].length-1];
      lDivisionPercentage = Math.round(lDivisionPercentage * 100000) / 100000;
      var lDivisionRank = lMatchObject[lAthleteDivisionTieBreak].length - lMatchObject[lAthleteDivisionTieBreak].lastIndexOf(lTieBreakScore);
    }
    lOutputArray.push([lOverallPercentage, lOverallRank, lDivisionPercentage, lDivisionRank, lTieBreakScore]);
  }
}
console.log(lOutputArray);
return lOutputArray;
SpreadsheetApp.flush();

}

function addAndSort(arr, val) {
    arr.push(val);
    i = arr.length - 1;
    item = arr[i];
    while (i > 0 && item < arr[i-1]) {
        arr[i] = arr[i-1];
        i -= 1;
    }
    arr[i] = item;
    return arr;
}

function onEdit() {
  updateLastEditDate();
};

function updateLastEditDate() {
  SpreadsheetApp.getActiveSpreadsheet().getRange('BC1').setValue(new Date().toTimeString());
};

Rob
  • 1
  • 1
  • 1
    Hey there, is the data not written to the sheet or you don't retrieve it using `IMPORTRANGE`? Moreover, do you have a mock-up sheet that you can share in order to test this? Do you have other functions as well associated with this sheet? – ale13 Apr 09 '21 at 07:40
  • Hey @ale13, I think I've sort of got to the root of the problem, and it seems to do with 'anonymous user' permissions, and closing the sheet from a normal user... but it's a bizarre issue. Basically if a user has a sheet open, the custom functions all work and return the data to the page normally, and an Anonymous User can see the data. If the user closes the sheet, after 5 mins an Anonymous User can no longer see the data. This seems to be trickled through via IMPORTRANGE through multiple sheets. The workaround is to turn the function into a more static function and use onEdit() instead. – Rob Apr 10 '21 at 08:20
  • Rob if I am understanding this correctly, you are using custom spreadsheet functions to calculate values which are then pulled off a publicly published version of the spreadsheet. Is that right? Can you share the details of how exactly you are publishing the spreadsheet (csv or other)? – iansedano Apr 13 '21 at 07:44
  • not quite... the source data is in the sheet with the customFunction (ie. contained). I've then got a 2nd sheet that references the data (IMPORTRANGE) and that 2nd sheet is shared to 'Anyone with the link', and that is published to a webpage. path: data (with customFunction) -> Shared -> Webpage If the 1st data sheet is open, then the info is visible on the webpage. If I close all the sheets in the browser, around 10 mins later the data is no longer visible on the webpage ??? Merely by reopening the data sheet, the data is visible on the webpage again. I gave up and now use a trigger. – Rob Apr 15 '21 at 08:05
  • If you solved your question consider writing an answer and accepting it. I have a couple more questions about it. At what point exactly does the data fail to propagate from the source to the Wordpress site? Is it present in the intermediate sheets? How about in the published URL? This may be a relevant discussion too - https://stackoverflow.com/questions/62219732/ . I'm going to set up some tests and see what I can uncover (that said, I think using custom formulae for this application will be quite headache prone, compared to plain triggered apps script) – iansedano Apr 16 '21 at 08:53
  • A good few days have passed and the published site is still showing data. I set it up so a source file uses IMPORTHTML and also uses that imported HTML to make a calculation with a custom formula. Then another sheet uses IMPORTRANGE to get everything from the first one. Then the second sheet publishes all its data. So again, I'd be inclined to say that the problem is on the Wordpress side, but since you say that onEdit solves it, I'm not sure any more. Can you clarify at which stage the data failed to propagate? – iansedano Apr 19 '21 at 07:01
  • @iansedano; Let me see if I can set up a test set of files and share them here. When I 'solved it', I ended up by writing a function to read data from the sheet and then write the data back to the sheet, rather than letting the customFunction return an array that 'spilled' from the cell where it was called. I then merely called that function in onEdit as this particular sheet is static until data is inserted into it. So I didn't really solve the underlying problem, I rather just worked around it. – Rob Apr 19 '21 at 08:11
  • @iansedano; Thinking further on your comment the external access by the Wordpress to display the sheet on the page, might be setting the user to 'Anonymous' down the chain and then feeding that 'Anonymous user' back up to the data sheet. Looks like this stackoverflow.com/questions/49225554 is the same problem. It's a bit bizarre, as the data sheet is not public, only the 'web view sheet' which basically only has an IMPORTRANGE function in it. I can't think why this would be a security issue and be locked down the way it is. – Rob Apr 19 '21 at 08:22
  • Thats odd because in my tests I also had a custom function that "spilled out" too. My sheets were all private bar the published URL, which was public. From the question you linked it seems like something may have been requiring your custom spreadsheet function to _calculate_ while closed, which it will not do as its like having an anonymous run your apps script functions, which will not be allowed. In my test the custom function only ran when I had it open, and once closed, it would not update again. Post back with the results from your test. – iansedano Apr 20 '21 at 07:02

0 Answers0