Как настроить триггеры Apps Script на основе времени для успешного выполнения?

Я реализовал 2 триггера в сценарии приложений. Цель этих триггеров — выполняться только 15-го и последнего дня каждого месяца. Текущая проблема: триггеры пытаются выполниться несколько раз в день, что приводит к исключению ошибки: в этом сценарии слишком много триггеров. Триггеры необходимо удалить из сценария, прежде чем можно будет добавить новые.

Триггер 1 (база данных копирования): Ожидаемые результаты: триггер копирует указанный диапазон из исходного листа (индекс) в целевой лист (все рабочие задания) 15-го числа каждого месяца и в последний день каждого месяца (с 23:00 до полуночи).

/// TRigger

ScriptApp.newTrigger("copyDatabase")
   .timeBased()
   .atHour(23)
   .everyDays(1) 
   .create();

function copyDatabase() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Index");
  var desinationsheet = ss.getSheetByName("All Work Orders");

  var startRow = 2; 
  var numRows = sheet.getLastRow() - startRow + 1; 
  var startCol = 23;
  var numCols = 14;
  var startColTarget = 1;
  var dataRange = sheet.getRange(startRow, startCol, numRows, numCols); 
  var data = dataRange.getValues(); 
  var Copy = "Copy"; 
  var firstEmptyRow = sheet.getRange("A2:P").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
  var dataToCopy = data.filter(row => row[2] !== Copy);

  desinationsheet.getRange(firstEmptyRow, startColTarget, dataToCopy.length, dataToCopy[0].length).setValues(dataToCopy);
}

Триггер 2 (exportTriggerFunction): Ожидаемые результаты: триггер выполняет функцию и экспортирует в указанные листы/вкладки в Excel 15-го числа (с 23:00 до полуночи) и в последний день каждого месяца (с 23:00 до полуночи).

///////////////
/// TRigger

ScriptApp.newTrigger("exportTriggerFunction")
   .timeBased()
   .atHour(23)
   .everyDays(1) 
   .create();

function exportTriggerFunction()
{
  var today = new Date();
  var lastDayOfMonth = new Date(today.getFullYear(), today.getMonth()+1, 0);

  if (today.getDate() == lastDayOfMonth.getDate() )
  {
var exportSheetIds = 
  ["560568812","1440952465","439649389","513481136",
   "1088500659","133657514","1618608630","802444630",
   "1834450484","657423004","682313931","1980296394","635144452"]; // Please set the sheet IDs you want to export.

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var name = sheet.getRange('Reference!M2').getDisplayValue();
  var destination = DriveApp.getFolderById("1HRchNqQ5_0LYzfULw1hWN_ALNuijsy2q"); // Google Drive Folder Location

  // 1. Copy the active Spreadsheet as a tempora Spreadsheet.
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet().copy('tmp');
  const sheets = spreadsheet.getSheets();
  const check = sheets.filter(s => exportSheetIds.includes(s.getSheetId().toString()));
  if (check.length == 0) {
    throw new Error("No export sheets.");
  }

  // 2. Convert the formulas to the texts.
  sheets.forEach(sheet => {
    if (exportSheetIds.includes(sheet.getSheetId().toString())) {
      const targetRange = sheet.getDataRange();
      targetRange.copyTo(targetRange, { contentsOnly: true });
    }
  });

  // 3. Delete/Exclude sheets: Index and Reference
  sheets.forEach(sheet => {
    if (!exportSheetIds.includes(sheet.getSheetId().toString())) {
      spreadsheet.deleteSheet(sheet);
    }
  });
  SpreadsheetApp.flush();

  // 4. Retrieve the blob from the export URL.
  const id = spreadsheet.getId();
  const xlsxBlob = UrlFetchApp.fetch(`https://docs.google.com/spreadsheets/export?id=${id}&exportFormat=xlsx`, { headers: { authorization: `Bearer ${ScriptApp.getOAuthToken()}` } }).getBlob();

  // 5. Create the blob as a file.
  destination.createFile(xlsxBlob.setName(`${name}.xlsx`));

  // 6. Delete the temporate Spreadsheet.
  DriveApp.getFileById(id).setTrashed(true);

  }
}

Изначально моя стратегия выполнения 15-го числа заключалась в настройке параметров каждого триггера.

Выберите тип триггера по времени: Месячный таймер. Выберите день месяца: 15 число.

А для выполнения в последний день месяца я попытался отредактировать сценарий кода, но эта комбинация в настоящее время вызывает исключение ошибки.

Сосредоточьте вопрос на одном из триггеров

Wicket 24.06.2024 16:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
66
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Во-первых, если вы ищете триггер, который срабатывает только 15-го и последнего дня каждого месяца, вы настраиваете его неправильно. Вам следует использовать триггер timeBased, который использует синтаксис onMonthDay.

Во-вторых, я не знаю, где в вашем коде вы настроили возможность программного добавления этих триггеров, но вы запускали скрипт столько раз, что было добавлено много копий. Я бы это почистил.

Если вам не нужно добавлять триггеры программно, я бы просто установил их вручную, используя onMonthDay. Я бы также использовал 1-й день месяца, полночь-1 час ночи, вместо последнего дня месяца (потому что вы не можете вручную установить последний день месяца напрямую, просто число).

Ответ принят как подходящий

Я считаю, что ваша цель состоит в следующем.

  • У вас есть 2 функции copyDatabase и exportTriggerFunction.
  • Эти функции работают нормально.
  • Вы хотите выполнить эти функции с помощью триггера, управляемого временем.
  • В вашей ситуации вы хотите выполнить обе функции copyDatabase и exportTriggerFunction только один раз с 23:00:00 до 00:00:00 15-го числа и в последний день каждого месяца.

В этом случае как насчет следующей модификации?

Точки модификации:

  • Когда я увидел, что ваш сценарий — это ваш настоящий сценарий, мне показалось, что ScriptApp.newTrigger("copyDatabase")... и ScriptApp.newTrigger("exportTriggerFunction")... используются как глобальные. В этом случае при запуске copyDatabase и exportTriggerFunction устанавливаются 2 триггера, управляемых по времени. И триггеры не удаляются. Я предположил, что ваше изображение может указывать на это.

Как насчет следующего потока, чтобы достичь своей цели?

  1. Запустите скрипт для установки триггера, управляемого временем.
    • В данном случае из сегодняшней даты и времени выбирается 15-е число или последний день этого месяца.
  2. При запуске триггера, управляемого временем, запускаются функции copyDatabase и exportTriggerFunction. И устанавливается следующий триггер, управляемый временем.
    • В данном случае из сегодняшней даты и времени выбирается 15-е число или последний день этого месяца.
    • Здесь функции copyDatabase и exportTriggerFunction используются путем помещения в функцию типа wrapFunction. Судя по вашему сценарию, я предположил, что обе функции можно выполнить за 6 минут.

Когда этот поток отражается в вашем сценарии, он становится следующим.

Модифицированный скрипт:

Скопируйте и вставьте следующий сценарий в редактор сценариев. И, пожалуйста, запустите функцию вручную installTriggers. При этом устанавливается 1-й триггер, управляемый по времени. После этого функции copyDatabase и exportTriggerFunction запускаются автоматически с помощью автоматически установленного триггера, управляемого по времени.

В этой модификации используется только один триггер, управляемый по времени.

// --- I added the below script.
function installTriggers() {
  // These values are from your question.
  const time = 23;
  const dates = [15, 0];


  const extraTime = 60; // seconds
  const functionName = "wrapFunction";
  ScriptApp.getProjectTriggers().forEach(t => {
    if (t.getHandlerFunction() == functionName) {
      ScriptApp.deleteTrigger(t);
    }
  });
  const now = Date.now();
  const triggerDate = dates.map(e => {
    const d = new Date();
    d.setMonth(d.getMonth() + (e > 0 ? 0 : 1), e);
    d.setHours(time, 0, 0);
    if (d.getTime() < now + (extraTime * 1000)) {
      d.setMonth(d.getMonth() + 1);
    }
    return d;
  }).sort((a, b) => a.getTime() > b.getTime() ? 1 : -1)[0];
  ScriptApp.newTrigger(functionName).timeBased().at(triggerDate).create();
}

function wrapFunction() {
  installTriggers();
  copyDatabase();
  SpreadsheetApp.flush();
  exportTriggerFunction();
}
// ---



// --- Below script is your showing script.
function copyDatabase() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Index");
  var desinationsheet = ss.getSheetByName("All Work Orders");

  var startRow = 2; 
  var numRows = sheet.getLastRow() - startRow + 1; 
  var startCol = 23;
  var numCols = 14;
  var startColTarget = 1;
  var dataRange = sheet.getRange(startRow, startCol, numRows, numCols); 
  var data = dataRange.getValues(); 
  var Copy = "Copy"; 
  var firstEmptyRow = sheet.getRange("A2:P").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
  var dataToCopy = data.filter(row => row[2] !== Copy);

  desinationsheet.getRange(firstEmptyRow, startColTarget, dataToCopy.length, dataToCopy[0].length).setValues(dataToCopy);
}

function exportTriggerFunction()
{
  var today = new Date();
  var lastDayOfMonth = new Date(today.getFullYear(), today.getMonth()+1, 0);

  if (today.getDate() == lastDayOfMonth.getDate() )
  {
var exportSheetIds = 
  ["560568812","1440952465","439649389","513481136",
   "1088500659","133657514","1618608630","802444630",
   "1834450484","657423004","682313931","1980296394","635144452"]; // Please set the sheet IDs you want to export.

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var name = sheet.getRange('Reference!M2').getDisplayValue();
  var destination = DriveApp.getFolderById("1HRchNqQ5_0LYzfULw1hWN_ALNuijsy2q"); // Google Drive Folder Location

  // 1. Copy the active Spreadsheet as a tempora Spreadsheet.
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet().copy('tmp');
  const sheets = spreadsheet.getSheets();
  const check = sheets.filter(s => exportSheetIds.includes(s.getSheetId().toString()));
  if (check.length == 0) {
    throw new Error("No export sheets.");
  }

  // 2. Convert the formulas to the texts.
  sheets.forEach(sheet => {
    if (exportSheetIds.includes(sheet.getSheetId().toString())) {
      const targetRange = sheet.getDataRange();
      targetRange.copyTo(targetRange, { contentsOnly: true });
    }
  });

  // 3. Delete/Exclude sheets: Index and Reference
  sheets.forEach(sheet => {
    if (!exportSheetIds.includes(sheet.getSheetId().toString())) {
      spreadsheet.deleteSheet(sheet);
    }
  });
  SpreadsheetApp.flush();

  // 4. Retrieve the blob from the export URL.
  const id = spreadsheet.getId();
  const xlsxBlob = UrlFetchApp.fetch(`https://docs.google.com/spreadsheets/export?id=${id}&exportFormat=xlsx`, { headers: { authorization: `Bearer ${ScriptApp.getOAuthToken()}` } }).getBlob();

  // 5. Create the blob as a file.
  destination.createFile(xlsxBlob.setName(`${name}.xlsx`));

  // 6. Delete the temporate Spreadsheet.
  DriveApp.getFileById(id).setTrashed(true);

  }
}

Примечание

  • В этой модификации ваши ScriptApp.newTrigger("copyDatabase")... и ScriptApp.newTrigger("exportTriggerFunction")... удалены. Если их не удалить, этот скрипт не сможет корректно работать. Пожалуйста, будьте осторожны с этим.

Использованная литература:

спасибо за очень подробное и понятное руководство. Изучив ваши комментарии, я заново собрал скрипты, и они работают как положено, вполне достаточно. Также спасибо за дополнительные ссылки для обзора.

Jarvis Davis 25.06.2024 23:56

@Джарвис Дэвис Спасибо за ответ и тестирование. Я рад, что ваша проблема была решена. И тебе спасибо.

Tanaike 26.06.2024 01:24

Другие вопросы по теме