Проблема с максимальным временем выполнения скрипта Google Apps - PullRequest
2 голосов
/ 27 апреля 2020

Я новичок в кодировании, и недавно я создал скрипт Google (на основе двух других скриптов), который выполняет следующее:

  1. Поиск черновика Gmail по строке темы
  2. Получает черновик Gmail и использует его в качестве шаблона для создания нескольких черновиков с уникальными вложениями
  3. Помещает фразу подтверждения после создания черновиков.

Вот код:

//Change these to match the column names you are using for email recepient addresses and merge status column//
var RECIPIENT_COL  = "Email";
var MERGE_STATUS_COL = "M";

//Creates the menu item "Mail Merge" for user to run scripts on drop-down//
function onOpen(e) {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Mail Merge')
  .addItem('? Create Drafts', 'createDrafts').addToUi(); 
}

function createDrafts() {
  // search for the draft Gmail message to merge with by its subject line
  var subjectLine = Browser.inputBox("Select draft " + "to merge with:", "Paste the subject line:", Browser.Buttons.OK_CANCEL);

  if (subjectLine === "cancel" || subjectLine == ""){ 
   // if no subject line finish up
   return;
  }

  // get the draft Gmail message to use as a template
  var emailTemplate = getGmailTemplateFromDrafts_(subjectLine);
  emailTemplate.subject = subjectLine;

  // get the data from the active sheet
  var sheet = SpreadsheetApp.getActiveSheet();
  var dataRange = sheet.getDataRange();
  // fetch values for each row in the Range.
  var data = dataRange.getValues();
  // assuming row 1 contains our column headings
  var header = data.shift(); 

  // get the index of column named 'M' (Assume header names are unique)
  var draftCreatedColIdx = header.indexOf(MERGE_STATUS_COL);

  var object = data.map(function(row) {  
    // create a new object for next row using the header as a key
    var nextRowObject = header.reduce(function(accumulator, currentValue, currentIndex) {
      accumulator[currentValue] = row[currentIndex];      
      return accumulator;
    }, {}) // Use {} here rather than initialAccumulatorValue
    return nextRowObject;
  });

  // loop through all the rows of data
  object.forEach(function(row, rowIdx){

    // only create drafts if mail merge status cell is blank
    if (row[MERGE_STATUS_COL] === ''){

        var msgObj = fillInTemplateFromObject_(emailTemplate, row);
        var attachment_id = "File Name";

        // split the values taken from cell into array
        var pdfName = row[attachment_id].split(', ');
          // initialize files as empty array
          var files = []; 

          // run through cell values and perform search
          for(var j in pdfName){ 
            // perform the search,results is a FileIterator
            var results = DriveApp.getFilesByName(pdfName[j]); 
            // interate through files found and add to attachment results
            while(results.hasNext()) {
        // add files to array
        files.push(results.next());
            }
          }     

        // @see https://developers.google.com/apps-script/reference/gmail/gmail-app#sendemailrecipient-subject-body-options
        GmailApp.createDraft(row[RECIPIENT_COL], msgObj.subject, msgObj.text, {htmlBody: msgObj.html, attachments: files});
        // create a confirmation phrase in the first column
        sheet.getRange("A" + (rowIdx + 2)).setValue("DRAFT");    
     }
  }); 
}

/**
 * Get a Gmail draft message by matching the subject line.
 * @param {string} subject_line to search for draft message
 * @return {object} containing the plain and html message body
*/
function getGmailTemplateFromDrafts_(subject_line) {
  try {
    // get drafts
    var drafts = GmailApp.getDrafts();
    // filter the drafts that match subject line
    var draft = drafts.filter(subjectFilter_(subject_line))[0];
    // get the message object
    var msg = draft.getMessage();
    return {text: msg.getPlainBody(), html:msg.getBody()};
  } catch(e) {
    throw new Error("Oops - can't find Gmail draft");
  }
}

/**
 * Filter draft objects with the matching subject linemessage by matching the subject line.
 * @param {string} subject_line to search for draft message
 * @return {object} GmailDraft object
*/
function subjectFilter_(subject_line){
  return function(element) {
    if (element.getMessage().getSubject() === subject_line) {
      return element;
    }
  }
}

/**
 * Fill HTML string with data object.
 * @param {string} template string containing {{}} markers which are replaced with data
 * @param {object} data object used to replace {{}} markers
 * @return {object} message replaced with data
 * H/T https://developers.google.com/apps-script/articles/mail_merge
*/
function fillInTemplateFromObject_(template, data) {
  // convert object to string for simple find and replace
  template = JSON.stringify(template);
  // Search for all the variables to be replaced, for instance {{Column name}}
  var templateVars = template.match(/{{([^}]+)}}/g);

  // Replace variables from the template with the actual values from the data object.
  // If no value is available, replace with the empty string.
  for (var i = 0; i < templateVars.length; ++i) {
    // strip out {{ }} 
    var variableData = data[templateVars[i].substring(2, templateVars[i].length - 2)];
    template = template.replace(templateVars[i], variableData || "");
  }
  // convert back to object
  return JSON.parse(template);
}

Сценарий работает, как и ожидалось, но когда я пытаюсь обработать слишком много строк со слишком большим количеством вложений , он превышает 6-минутное максимальное время выполнения скрипта Google.

При попытке решить эту проблему я нашел простой скрипт , который использует continuationToken и при этом никогда не превышает лимит. Моя цель - попытаться использовать тот же принцип в моем собственном сценарии и обработать строки десятками. К сожалению, мне пока не повезло, и мне нужна помощь. Вот код скрипта, который я нашел:

Code.gs

function onOpen() {
  SpreadsheetApp.getUi().createMenu("List Drive files").addItem('Start', 'start').addToUi();
}

function start() {
  var ui = HtmlService.createHtmlOutputFromFile('ui');
  return SpreadsheetApp.getUi().showSidebar(ui);
}

function getDriveFiles(continuationToken) {
  if(continuationToken) {
    var files = DriveApp.continueFileIterator(continuationToken);
  }
  else {
    var files = DriveApp.getFiles();
  }
  var i = 0;
  while (files.hasNext() && i < 10) {
    var file = files.next();
    SpreadsheetApp.getActiveSheet().appendRow([file.getName(), file.getUrl()]);
    i++;
    if(i == 10) {
      return files.getContinuationToken();
    }
  }
} 

ui. html

<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<div style="text-align:center; margin-top:10px">
<div>Files processed:</div>
<div id="nbOfFilesProcessed">0</div>
<br>
<button id="startButton" class="blue" onclick="start()">Start</button>
<div class="secondary">Close the sidebar to stop the script.</div>
</div>

<script>
function start() {
  document.getElementById("startButton").disabled = true;
  google.script.run.withSuccessHandler(onSuccess).getDriveFiles();
}
function onSuccess(continuationToken){
  // If server function returned a continuationToken it means the task is not complete
  // so ask the server to process a new batch.
  if(continuationToken) {
    var nbOfFilesProcessedEl = document.getElementById("nbOfFilesProcessed");
    nbOfFilesProcessedEl.innerHTML = parseInt(nbOfFilesProcessedEl.innerHTML) + 10;
    google.script.run.withSuccessHandler(onSuccess).getDriveFiles(continuationToken);
  }
}
</script> 

1 Ответ

1 голос
/ 28 апреля 2020

Из того, что я вижу в опубликованном вами коде, вам нужно будет отредактировать вашу функцию createDrafts следующим образом:

  • Изменить способ запуска функции: вам придется использовать HTML Элемент пользовательского интерфейса для запуска javascript внутри него.
  • Отредактируйте while l oop, чтобы он имел оператор return, когда вы достигнете предела своей партии.
  • Создайте функцию Javascript в элементе пользовательского интерфейса HTML, которая обрабатывает успешное выполнение функции createDrafts и рекурсивно вызывает ее в случае, если возвращается continuationToken.

Snippets

Компонент пользовательского интерфейса

Вы можете сохранить свое пользовательское меню и по щелчку добавить это HTML в диалоговое окно пользовательского интерфейса.

- code.gs -
//Creates the menu item "Mail Merge" for user to run scripts on drop-down//
function onOpen(e) {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Mail Merge')
  .addItem('? Create Drafts', 'openDialog').addToUi(); 
}
function openDialog() {
  // Display a modal dialog box with custom HtmlService content.
  var htmlOutput = HtmlService
      .createHtmlOutputFromFile('Dialog')
      .setWidth(250)
      .setHeight(300);
  SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Create Drafts');
}
- Dialog.html -
<!-- The UI will be very similar to the one you found, I will keep only the strictly necessary statements for this example -->
<div>
<button id="startButton" onclick="startBatch()">Start</button>
</div>

<script>
function startBatch() {
  google.script.run.withSuccessHandler(onSuccess).createDrafts();
}
function onSuccess(continuationToken){
  // If server function returned a continuationToken it means the task is not complete
  // so ask the server to process a new batch.
  if(continuationToken) {
  google.script.run.withSuccessHandler(onSuccess).createDrafts(continuationToken);
  }
}
</script> 

Компонент скрипта приложения

function createDrafts(continuationToken) {
 var batchLimit = 10;
 // ...
 // run through cell values and perform search
 for(var j in pdfName){ 
     // perform the search,results is a FileIterator
     if (continuationToken) {
         var results = DriveApp.continueFileIterator(continuationToken);
     } else {
         var results = DriveApp.getFilesByName(pdfName[j]);
     } 
     // interate through files found and add to attachment results
     let i = 0;
     while(results.hasNext() && i<batchLimit) {
         // add files to array
         files.push(results.next());
         i++;
         if (i === batchLimit) {
             return results.getContinuationToken();
         }
     }
 }     

Заключительные замечания

В качестве улучшения вашей пакетной операции я бы сохранил все пользовательские данные, чтобы вы могли продолжить выполнение сценария, не запрашивая его снова. Вы можете передать эти значения в функцию возврата объекта javascript или сохранить их в кеше с помощью утилиты CacheService.

Кроме того, попробуйте найти правильный компромисс между временем выполнения и лимитом пакета: A ограничение малых партий никогда не превысит ограничение по времени, но очень быстро израсходует вашу квоту.

Ссылки:

API на стороне клиента

Кэш Сервис

Интерфейс скрипта приложений

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...