【问题标题】:How to fix an OnChange script that worked perfectly, then stopped working when the Google Sheet was modified如何修复运行良好的 OnChange 脚本,然后在修改 Google 表格时停止工作
【发布时间】:2020-01-07 07:29:02
【问题描述】:

概览:工作簿根据每条记录的类别得分计算每条记录(或行)的总分。类别分数源自对每条记录的数百个数据点执行的计算。根据我在网上找到的几个示例和教程,我编译了代码以发送名为 checkComplete(如下)的 onChange 电子邮件。 checkComplete 只会在记录状态更改为“完成”时发送电子邮件。由于工作簿使用 Importrange 加载原始数据,因此需要 onChange 触发器,并且 onEdit 触发器不会发生人工交互。

当我注意到在进行任何更改时会发送一封电子邮件时,问题就开始了,而不仅仅是 O 列中的状态从空白单元格更改为“完成”。但是代码运行良好,直到我对 Google 表格工作簿进行了重大修改,其中包括:

  1. 修改计算公式以显示一组小数

    例如=if(round('2-Comp'!F14,4)=0,"",round('2-Comp'!F14,4))

  2. 移动列

    例如切换列 N 和 O,其中 N 是 onChange 触发器检查的原始列

  3. 更新源数据,从而更新分数。

    例如记录的数据点从 $52,420 更改为 $54,323

然后电子邮件轰炸开始了。最终结果是脚本开始为从第一条记录开始的每条记录发送一封电子邮件,并会连续生成以下错误消息:

一天服务调用次数过多:电子邮件。 (第 47 行,文件“checkComplete”)

超过最大执行时间

一天使用过多计算机时间的服务

我已经注释掉 MailApp.sendEmail() 以停止炸毁我的收件箱,但脚本继续生成“超过最大执行时间”和“一天使用过多计算机时间的服务”。我的想法是脚本正在处理上述所有更改,但现在这个问题已经持续了一个多星期,我决定寻求帮助。

我认为问题出在数组上,特别是 var oldValues,所以我尝试了:

  1. 调用flush()函数清空数组,没有解决问题。

  2. 按照这个线程清空数组 - How do I empty an array in JavaScript?,但这也没有解决问题。

我现在认为问题可能与记录器有关,但说实话我很难过。

以下是脚本的完整源代码:

checkComplete.gs

var admin_email='xxxxxxx@xxxxxxx.com'; //<- list of email recipients goes here

function checkComplete() {
  var sh = SpreadsheetApp.getActiveSheet();
  var values = sh.getRange('O14:O').getValues().join('-');
  if(PropertiesService.getScriptProperties().getKeys().length==0){ // first time you run the script
  PropertiesService.getScriptProperties().setProperty('oldValues', values);
  return;
}
  var oldValues = PropertiesService.getScriptProperties().getProperty('oldValues').split('-');
  var valuesArray = values.split('-');
  while (valuesArray.length>oldValues.length){
    oldValues.push('x'); // if you append some rows since last exec
  }
  Logger.log('oldValues = '+oldValues)
  Logger.log('current values = '+valuesArray)
  for(var n=0;n<valuesArray.length;n++){
    if(oldValues[n] != valuesArray[n]){ // check for any difference
      sendMail(n+1,valuesArray[n]);
    }
  }
   PropertiesService.getScriptProperties().setProperty('oldValues', values);

}
function sendMail(row,val){
  Logger.log('value changed on row '+row+' value = '+val+' ,  mail sent');
  var rank = SpreadsheetApp.getActiveSheet().getRange(row,1).getValue();
// other var that I deleted here to shorten this post
// MailApp.sendEmail(admin_email,'message that calls vars');

}

我希望仅在记录状态(在 O 列中指示)从空白单元格更改为“完成”时收到电子邮件。所有其他数据更新不应触发任何电子邮件。任何建议/帮助表示赞赏。感谢您抽出宝贵时间查看我的问题。

【问题讨论】:

  • 格林威治标准时间,你发现了吗?复制你的代码对我来说没有问题。
  • 我可以看看你的电子表格吗?
  • @JonathanDavis,这令人鼓舞。不,错误日志每天都在继续增长。我想知道问题是什么。如果您有兴趣查看,我将在回复 zianotschka 时发布一个查看电子表格的链接。
  • @ziganotschka,当然,我刚刚打开电子表格以在此处查看 docs.google.com/spreadsheets/d/…。仅供参考,当 MailApp.sendEmail() 被注释掉时,我继续收到包含以下两条消息的错误日志:“超过最大执行时间”和“一天内使用过多计算机时间的服务”。
  • @GMTSkunkworks 您是否设置了可安装触发器?我假设是这样,但如果是这样,您使用的是 onChange 还是 onEdit?我会使用菜单>当前项目触发器进行检查,以确保您没有为同一功能添加一堆触发器(这可能解释为一天错误调用的服务太多次)。如果是这样,您可以使用脚本快速删除它们。如果这是问题,我会提供代码

标签: google-apps-script google-sheets


【解决方案1】:

更新

在指定列 O 中的条目将由人类创建之后 并将触发onEdit() 触发器,这是一个简单而优雅的 使用event objects的解决方案:

 onEdit(e){
  if( e.range.getSheet().getName()=='Sheet1' &&
    e.range.getColumn()==15&&e.value=="Complete"&&e.oldValue!="Complete"){
    sendMail(e.range.getRow(),e.value);
   }  
}  

您可以将function checkComplete() 及其 onChange 触发器替换为 这个onEdit 函数。


查看您的电子表格后,我注意到以下问题:

  1. 正如我在评论中提到的,onEdit 将由电子表格中任何工作表的更改触发,但您希望代码仅针对工作表“仪表板”中的更改运行。此外,您不希望将Dashboard 中的列 O 与其他工作表中的列 O 进行比较 - 所有条目都将不同,并会触发对 sendMail 的调用。在继续执行其余代码之前,您应该在 "Dashboard 的开头实现一个条件语句来验证您是否在正确的工作表中。

  2. 你的函数sendMail包含

  var rank = SpreadsheetApp.getActiveSheet().getRange(row,1).getValue();
  var totalranked = SpreadsheetApp.getActiveSheet().getRange(4,1).getValue();
  var address = SpreadsheetApp.getActiveSheet().getRange(row,2).getValue();
  var score = SpreadsheetApp.getActiveSheet().getRange(row,6).getValue();
  var outlook = SpreadsheetApp.getActiveSheet().getRange(row,14).getValue();
  var ildemand = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,9).getValue();
  var aldemand = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,14).getValue();
  var mcdemand = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,19).getValue();
  var increase = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('2-Comp').getRange(row,11).getValue();
  var occupancy = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('2-Comp').getRange(row,13).getValue();
  var medHHI = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,17).getValue();
  var medHV = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,19).getValue();
  var walkscore = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,19).getValue();
  var dmnd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,5).getValue()
  var comp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('2-Comp').getRange(row,5).getValue();
  var muni = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,5).getValue();
  var grade = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,17).getValue();
  var demo  = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,5).getValue();

这是对 SpreadsheetApp 的 18 次调用,每次调用 sendMail。如果您将两个不同工作表的 O 列相互比较(参见 1.),则乘以 sendMail 将被调用的时间量!请注意,对外部服务的任何请求都非常耗时且效率低下,应避免,请参阅Best Practices。在sendMail 之外,您应该只检索一次所有这些变量。鉴于此后sendMail 的代码将很短,您可以直接在for 循环中实现它。

  1. 如果在工作表的开头而不是结尾附加了一行 - 这会将新值与旧值相比较,因此,每一行都会触发 sendMail 的调用。您可以避免使用 indexOf 检查新内容是否包含在旧内容的数组中。

  2. 从 Apps 脚本编辑器 UI 转到 Edit-&gt;Current project's triggers 并检查您是否不小心将多个触发器附加到文件,这些触发器都同时运行。

考虑到第 1.、2. 和 3. 点,以下是对您的代码的改进建议:


var admin_email=XXXXX; //<- list email addresses here

function checkComplete() {
  var ss=SpreadsheetApp.getActive();
  var sh = ss.getActiveSheet();
  if(sh.getName()=="Dashboard"){
   var values = sh.getRange('O14:O').getValues().join('-');
   if(PropertiesService.getScriptProperties().getKeys().length==0){ // first time you run the script
    PropertiesService.getScriptProperties().setProperty('oldValues', values);
    return;
   }
   var oldValues = PropertiesService.getScriptProperties().getProperty('oldValues').split('-');
   var valuesArray = values.split('-');
   while (valuesArray.length>oldValues.length){
    oldValues.push('x'); // if you append some rows since last exec
   }
   Logger.log('oldValues = '+oldValues)
   Logger.log('current values = '+valuesArray)
   for(var n=0;n<valuesArray.length;n++){
    if(oldValues.indexOf(valuesArray[n])==-1){ // check for any difference
        Logger.log('value changed on row '+n+1+' value = '+valuesArray[n]+' ,  mail sent');
      sendMail(n+1,valuesArray[n]);
    }
   }  
   PropertiesService.getScriptProperties().setProperty('oldValues', values);
  }   
}


function sendMail(row,val){

   Logger.log('value changed on row '+row+' value = '+val+' ,  mail sent');

   var rank = sh.getRange(row,1).getValue();
   var totalranked = sh.getRange(4,1).getValue();
   var address = sh.getRange(row,2).getValue();
   var score = sh.getRange(row,6).getValue();
   var outlook = sh.getRange(row,14).getValue();
   var ildemand = ss.getSheetByName('1-Dmnd').getRange(row+1,9).getValue();
   var aldemand = ss.getSheetByName('1-Dmnd').getRange(row+1,14).getValue();
   var mcdemand = ss.getSheetByName('1-Dmnd').getRange(row+1,19).getValue();
   var increase = ss.getSheetByName('2-Comp').getRange(row,11).getValue();
   var occupancy = ss.getSheetByName('2-Comp').getRange(row,13).getValue();
   var medHHI = ss.getSheetByName('4-Demo').getRange(row+2,17).getValue();
   var medHV = ss.getSheetByName('4-Demo').getRange(row+2,19).getValue();
   var walkscore = ss.getSheetByName('3-Muni').getRange(row,19).getValue();
   var dmnd = ss.getSheetByName('1-Dmnd').getRange(row+1,5).getValue()
   var comp = ss.getSheetByName('2-Comp').getRange(row,5).getValue();
   var muni = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,5).getValue();
   var grade = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,17).getValue();
   var demo  = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,5).getValue();


      // MailApp.sendEmail(admin_email,'EARLY results for '+address+' are ready!','EARLY says the Site is '+outlook+' and ranks # '+rank+' out of '+totalranked+' with an overall Score of '+score+'. The projected Net Demand is '+dmnd+' with: IL '+ildemand+' units, AL '+aldemand+' units and MC '+mcdemand+' units. The competitive environment is '+comp+' with an occupancy rate of '+occupancy*100+'% for nearby properties, and an expected supply increase of '+increase*100+'%. The location is '+walkscore+', and its public schools have a grade of '+grade+' making it '+muni+'. The demographic trends are '+demo+' with Median HHI of $'+medHHI+' and a median Home Value of $'+medHV+'. For more information, please visit https://docs.google.com/spreadsheets/d/1ydcXQilx6hxhI6HPpWTPT7Bq9a-gXPa7h8UC6KL9W8c/edit?usp=sharing.');
    // add senior growth rate(s) to the Demographic summary sentence - Sept 3 2019
}

【讨论】:

  • 感谢您的详细回复,并建议对代码进行编辑。至于您的观点: 1. 理解并同意; 2. 明白了,我会复习Best Practices,看看我是否能弄清楚如何实施您建议的解决方案; 3. 新数据将始终附加到下一个可用的空白行,永远不会附加到工作表的开头;并且,4. 我可以确认当前项目的触发器是“显示 1 个触发器”。我取消了 MailApp.sendEmail() 的注释,这样一旦 Google 重置一条新记录,就会将一条新记录添加到源工作簿中,以查看问题是否已解决。敬请期待……
  • 只需使用 MailApp.sendEmail() 运行代码一次,并且 sendMail 中的所有变量调用都已注释掉,因为在第一次运行脚本属性第一次填充之前,`if (oldValues.indexOf(valuesArray[n])==-1)` 将始终实现
  • 谢谢!在我看到你的消息之前,昨天@ 4:22pm 我运行了脚本,@ 4:23pm 将仪表板表上的单元格 O71 和 O72 的状态更改为空白,然后更改为“完成”。脚本执行完成,但是当我没有收到任何电子邮件时,我认为我的帐户已经出现错误“一天使用过多计算机时间的服务”。那是我回复你的消息的时候。我按照您的指示注释掉您列出的项目,然后运行脚本。然后我取消注释这些项目,并测试更改单元格 O472。脚本执行完成,但未收到电子邮件。
  • 你的意图是什么?要从工作表中已有的所有行发送电子邮件还是仅发送新行?取消注释后插入新行以触发新电子邮件。
  • 好问题。从新行与现有行的角度考虑,答案将是新行被标记为“完成”时。旧行现在应该都是“完成”,因此应该已经发送了一封电子邮件。从导入到 0-Inputs 表的数据中填充一行。当我们的数据输入团队将值输入到源 Inputs 工作簿中时,0-Inputs 工作表开始逐个单元格地填充数据。出于安全原因,我们将两个工作簿分开。因此,只有在仪表板工作表上的新行标记为“完成”时才应发送电子邮件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-14
  • 1970-01-01
  • 2013-08-02
  • 2022-08-15
  • 2015-10-16
相关资源
最近更新 更多