Large Manager Hierarchy Template

Unter einem AdWords-Verwaltungskonto können Hunderte oder sogar Tausende von Kundenkonten verwaltet werden. Die Ausführung eines Skripts für alle Konten kann schwierig sein, denn bei der parallelen Verarbeitung mithilfe von executeInParallel können maximal 50 Konten pro Skriptausführung verarbeitet werden. Auch der sequenzielle Durchlauf der Konten ist problematisch, da bei der Skriptausführung eine Zeitüberschreitung auftreten kann, wenn Sie sehr viele Konten haben.

Das Skript "Large Manager Hierarchy Template" nimmt sich dieser Herausforderung an, indem bei jeder Ausführung eine andere Teilmenge von Konten verarbeitet wird. Bei jeder Ausführung werden die Zwischenergebnisse in einer temporären Datei auf Google Drive gespeichert, sodass bei nachfolgenden Ausführungen bekannt ist, welche Konten noch verarbeitet werden müssen. Durch mehrmalige Skriptausführung werden schließlich alle Konten durchlaufen und verarbeitet. Sobald die letzten verbleibenden Konten verarbeitet wurden, wird das Skript zurückgesetzt und ein neuer Verarbeitungszyklus beginnt, und zwar mit der von Ihnen festgelegten Häufigkeit.

Die Vorlage ist in zwei Abschnitte unterteilt. Der Abschnitt STANDARD TEMPLATE enthält die Funktionen zum Durchlaufen aller Konten, wobei das Skript gegebenenfalls mehrmals ausgeführt werden muss. Diesen Abschnitt müssen Sie nicht bearbeiten. Der Abschnitt YOUR IMPLEMENTATION enthält mehrere Platzhalterfunktionen, die auch als "Hooks" bezeichnet werden. Diese können Sie mit der Logik füllen, die definiert, wie Sie Ihre Konten tatsächlich verarbeiten möchten. Der Quellcode unten veranschaulicht eine Beispielimplementierung.

Auf dieser Seite bezieht sich der Begriff Ausführung auf eine einzelne Ausführung des Skripts. Der Begriff Zyklus bezeichnet dagegen alle einzelnen Skriptausführungen, die notwendig sind, um alle Konten zu verarbeiten.

Skript konfigurieren

Zum Verwenden des Skripts sind drei Schritte erforderlich:

  1. Erstellen Sie ein neues Skript aus der Vorlage.
  2. Legen Sie Parameter auf Vorlagenebene fest.
  3. Stellen Sie die Kernlogik bereit.

Neues Skript aus der Vorlage erstellen

Erstellen Sie ein neues Skript mit dem unten stehenden Quellcode. Geben Sie im Objekt TEMPLATE_CONFIG in FILENAME einen Namen für die Datei an, der verwendet wird, um die Zwischenergebnisse auf Drive zu speichern. Das Skript erstellt diese Datei bei der ersten Ausführung und verwendet sie dann bei nachfolgenden Ausführungen erneut. Sie können FILENAME beliebig festlegen, aber es wird empfohlen, einen Namen zu verwenden, der dem des Skripts ähnelt. So können Sie die Datei später bei Bedarf besser erkennen.

Der FILENAME muss in Ihrem Konto in allen Skripts, die diese Vorlage verwenden, eindeutig sein. Andernfalls überschreiben die Zwischendaten aus verschiedenen Skripts sich gegenseitig. FILENAME muss eindeutig sein und darf nicht mit einer anderen Datei übereinstimmen, die sich bereits in Ihrem Drive-Konto befindet.

Parameter auf Vorlagenebene festlegen

Sie müssen mehrere Parameter auf Vorlagenebene festlegen.

  • MIN_FREQUENCY: Hiermit wird die Mindesthäufigkeit der Zyklen bestimmt. Der nächste Zyklus startet erst, wenn zumindest diese Anzahl von Tagen seit dem Start des vorherigen Zyklus verstrichen ist. Die tatsächliche Häufigkeit kann aber länger sein, wenn das Durchlaufen eines Zyklus mehr Zeit als MIN_FREQUENCY in Anspruch nimmt. Dies ist beispielsweise möglich, wenn eine große Anzahl von Konten verarbeitet wird oder das Skript selbst so selten geplant ist, dass nicht alle Konten innerhalb der in MIN_FREQUENCY festgelegten Tage verarbeitet werden können.
  • USE_PARALLEL_MODE: Hiermit wird festgelegt, ob das Skript die Konten parallel verarbeitet oder einzeln nacheinander durchläuft.
  • MAX_ACCOUNTS: Hiermit wird festgelegt, wie viele Konten das Skript maximal bei einer einzigen Ausführung verarbeitet. Wird das Skript im parallelen Modus ausgeführt, ist die tatsächliche Anzahl der Konten auf 50 begrenzt.
  • ACCOUNT_CONDITIONS (optional): Ein Array von ManagedAccountSelector-Bedingungen, mit denen gesteuert wird, welche Konten vom Skript verarbeitet werden. Sie können beispielsweise eine Bedingung wie 'LabelNames CONTAINS "ACCOUNT_LABEL"' hinzufügen, damit nur Konten mit einem bestimmten Label verarbeitet werden.

Im Abschnitt Planung unten finden Sie Vorschläge, wie Sie diese Parameter für verschiedene Anwendungsfälle einrichten können.

Kernlogik bereitstellen

Die Vorlage wäre unpraktisch, wenn Sie darin keine eigene Logik implementieren könnten, um festzulegen, wie Sie Ihre Konten tatsächlich verarbeiten möchten. Zu diesem Zweck enthält die Vorlage fünf Funktionen oder "Hooks", die zu bestimmten Zeiten in jedem Zyklus aufgerufen werden, so wie unten dargestellt. Sie können eine Implementierung für jeden Hook bereitstellen, um mit Ihrem Skript die notwendige Verarbeitung durchzuführen.

Für jeden Hook enthält die Vorlage eine Beispielimplementierung, die Sie durch Ihre eigene Logik ersetzen sollten. Sie können Ihrem Skript auch wie gewohnt eigene Funktionen und Variablen hinzufügen und von innerhalb dieser Hooks verwenden.

initializeCycle(customerIds)
Diese Funktion wird einmal zu Beginn jedes Zyklus mit der Liste der zu verarbeitenden Konten aufgerufen, die ACCOUNT_CONDITIONS entsprechen. Sie können damit eine Initialisierung durchführen, die Ihr Skript für einen ganzen Zyklus benötigt. Einige mögliche Anwendungsfälle:
  • Senden Sie eine E-Mail, um zu signalisieren, dass ein Zyklus begonnen hat.
  • Laden oder generieren Sie statische Daten, die bei der Skriptausführung während des Zyklus identisch sein sollen. Sie müssen diese Daten speichern, beispielsweise auf Drive oder in einer Tabelle, damit sie später bei jeder Ausführung geladen werden können.
Wenn eine Initialisierung nicht notwendig ist, lassen Sie die Implementierung leer.
initializeExecution(customerIds)
Diese Funktion wird einmal zu Beginn jeder Ausführung mit der Teilmenge der Konten aufgerufen, die bei dieser Ausführung verarbeitet werden. Sie können damit eine Initialisierung durchführen, die Ihr Skript für jede Ausführung benötigt. Einige mögliche Anwendungsfälle:
  • Senden Sie eine E-Mail, um zu signalisieren, dass eine Ausführung begonnen hat.
  • Laden Sie statische Daten, die bei allen Ausführungen identisch sein sollen (siehe oben).
Wenn eine Initialisierung nicht notwendig ist, lassen Sie die Implementierung leer.
processAccount()
Innerhalb eines Zyklus wird diese Funktion für jedes AdWords-Konto einmal aufgerufen. Verwenden Sie sie, um damit die Hauptverarbeitungslogik Ihres Skripts auszuführen, beispielsweise Berichte abrufen und analysieren, Kampagnen pausieren/Pausierung aufheben, Gebote ändern, Anzeigen erstellen, E-Mail-Benachrichtigung senden, Daten in einer Tabelle speichern oder andere Aufgaben, die mit AdWords-Skripts erledigt werden können. Optional kann diese Funktion beliebige Ergebnisse zurückgeben, die mit der Verarbeitung des Kontos verknüpft sind. Die Rückgabe von Ergebnissen ermöglicht Ihnen, die Ergebnisse verschiedener Konten vor der Ausgabe zu konsolidieren. So können Sie beispielsweise eine einzelne E-Mail-Benachrichtigung senden, die die Ergebnisse aus allen Konten enthält, anstatt nur eine E-Mail pro Konto (siehe unten).
processIntermediateResults(results)
Diese Funktion wird einmal am Ende jeder Ausführung mit den Ergebnissen aus den Konten aufgerufen, die bei dieser Ausführung verarbeitet werden. Verwenden Sie die Funktion, um die Ergebnisse der Ausführung zu analysieren, zu konsolidieren und auszugeben. Einige mögliche Anwendungsfälle:
  • Fügen Sie die Ergebnisse dieser Ausführung in eine Tabelle ein.
  • Erstellen und senden Sie eine Zusammenfassungs-E-Mail mit allen Ergebnissen dieser Ausführung.
Wenn keine Ergebnisse ausgegeben werden sollen bzw. erst nach Abschluss des gesamten Zyklus, lassen Sie Ihre Implementierung leer.
processFinalResults(results)
Diese Funktion wird einmal am Ende jedes Zyklus mit den Ergebnissen aus allen Konten aufgerufen, die in diesem Zyklus verarbeitet werden. Verwenden Sie die Funktion, um die Ergebnisse aus allen Konten, die im Zyklus enthalten sind, zu analysieren, zu konsolidieren und auszugeben. Einige mögliche Anwendungsfälle:
  • Führen Sie eine Analyse durch, für die Ergebnisse aus allen Konten erforderlich sind.
  • Fügen Sie alle Ergebnisse gleichzeitig in eine Tabelle ein.
  • Erstellen und senden Sie eine Zusammenfassungs-E-Mail mit den Ergebnissen aus allen Konten.
Wenn Sie noch keine Ergebnisse haben, die ausgegeben werden sollen, lassen Sie Ihre Implementierung leer.

Planung

Die Planung eines Skripts, in dem die Manager Account Template verwendet wird, ist etwas anspruchsvoller als bei anderen Skripts, da Sie in Form von Zyklen und nicht einzelner Ausführungen denken müssen. Die meisten Nutzer möchten jeden Zyklus so schnell wie möglich abschließen. Führen Sie dazu die folgenden Schritte aus:

  • Planen Sie in AdWords die stündliche Ausführung des Skripts. Das ist die höchste Häufigkeitseinstellung.
  • Schätzen Sie, wie lange die Ausführung der Implementierung processAccount() für ein einzelnes Konto dauert. Um einen Schätzwert erhalten, können Sie beispielsweise ACCOUNT_CONDITIONS festlegen und das Skript im sequenziellen Modus ausführen, damit es nur für einige Konten ausgeführt wird. Dann können Sie die Ausführungszeit durch die Anzahl der Konten dividieren.
  • Legen Sie USE_PARALLEL_MODE und MAX_ACCOUNTS wie folgt fest.
    • Ein Skript, das im sequenziellen Modus ausgeführt wird, kann 30 Minuten (1.800 Sekunden) laufen, bevor eine Zeitüberschreitung auftritt. Im parallelen Modus kann ein Skript für bis zu 50 Konten ausgeführt werden. In welchem Modus mehr Konten pro Ausführung verarbeitet werden, hängt davon ab, wie lange die Verarbeitung einzelner Konten durch das Skript dauert.
    • Wenn Sie schätzen, dass die Verarbeitungszeit pro Konto weniger als 36 Sekunden dauert (1.800/50), wird empfohlen, den sequenziellen Modus zu verwenden. Der parallele Modus wird dagegen empfohlen, wenn Sie schätzen, dass die Verarbeitung pro Konto länger als 36 Sekunden dauert. Um die Verarbeitungszeit pro Konto flexibler zu gestalten und noch etwas Zeit für die Nachbearbeitung einzuräumen, ist eine Dauer von 30 anstatt 36 Sekunden pro Konto angemessen.
    • Wenn Sie den parallelen Modus verwenden, legen Sie MAX_ACCOUNTS auf 50 fest. Die Angabe einer größeren Anzahl ist zwecklos, da der Wert auf maximal 50 beschränkt ist.
    • Wenn Sie den sequenziellen Modus verwenden, legen Sie MAX_ACCOUNTS auf die größtmögliche Anzahl von Konten fest, die innerhalb von 30 Minuten verarbeitet werden können. Wenn Sie beispielsweise geschätzt haben, dass jedes Konto in 10 Sekunden verarbeitet werden kann, könnten Sie MAX_ACCOUNTS auf 180 festlegen. In der Praxis empfiehlt es sich, eine etwas kleinere Zahl festzulegen, damit genügend Zeit bleibt, um die Implementierungen processIntermediateResults() und processFinalResults() auszuführen.

Obwohl Sie in AdWords für einzelne Ausführungen die Häufigkeit "Stündlich" festgelegt haben, können Sie MIN_FREQUENCY angeben, um die Zyklen zu definieren. Wenn Sie beispielsweise einmal wöchentlich einen Bericht im Tabellenformat generieren möchten, der alle Konten umfasst, erstellen Sie die Tabelle im Hook processFinalResults() und legen Sie MIN_FREQUENCY auf 7 fest.

Funktionsweise

Der Kernbestandteil der Vorlage ist im Bereich STANDARD TEMPLATE enthalten, in dem sich auch die main()-Funktion der Vorlage befindet. Ein stateManager-Objekt legt die Methoden zum Laden und Speichern des Zustands des Zyklus aus vorherigen Ausführungen offen.

Zu Beginn jeder Ausführung lädt main() den vorherigen Zustand, startet einen neuen Zyklus, wenn seit dem letzten abgeschlossenen Zyklus genügend Zeit verstrichen ist, ermittelt eine Teilmenge der zu verarbeitenden Konten, ruft den Hook initializeCycle() auf, falls zutreffend, und den Hook initializeExecution(). Anschließend wird die Methode executeByMode() aufgerufen, die mithilfe einer sequenziellen oder parallelen Strategie die Teilmenge der Konten verarbeitet. Beide Strategien rufen den Hook processAccount() für jedes Konto in der Teilmenge auf. Schließlich werden die Ergebnisse an die Methode completeExecution() übergeben, die sie auf Drive speichert. Abschließend ruft die Vorlage den Hook processIntermediateResults() auf und auch den Hook processFinalResults, wenn das Ende des Zyklus erreicht ist.

Die Vorlage ist zwar eigentlich für die Verwendung in einem Verwaltungskonto vorgesehen, kann aber auch erfolgreich in einem einzelnen Konto ausgeführt werden. Die Ausführung der Vorlage in einem einzelnen Konto kann hilfreich sein, wenn Sie Ihre Kernlogik entwickeln und mögliche Fehler beheben. Sie können auch ein Skript, das zuvor in einem Verwaltungskonto verwendet wurde, auf ein einzelnes Konto anwenden, ohne dass – abgesehen von FILENAME – irgendwelche Änderungen erforderlich sind.

Einrichtung

  • Erstellen Sie ein Skript mit dem unten stehenden Quellcode.
  • Führen Sie die Schritte unter Skript konfigurieren aus, um die Einrichtung abzuschließen.

Quellcode

// Copyright 2016, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @name Large Manager Hierarchy Template
 *
 * @overview The Large Manager Hierarchy Template script provides a general way
 *     to run script logic on all client accounts within a manager account
 *     hierarchy, splitting the work across multiple executions if necessary.
 *     Each execution of the script processes a subset of the hierarchy's client
 *     accounts that it hadn't previously processed, saving the results to a
 *     temporary file on Drive. Once the script processes the final subset of
 *     accounts, the consolidated results can be output and the cycle can begin
 *     again.
 *     See
 *     https://developers.google.com/adwords/scripts/docs/solutions/mccapp-manager-template
 *     for more details.
 *
 * @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 1.0
 *
 * @changelog
 * - version 1.0
 *   - Released initial version.
 */

/*************** START OF YOUR IMPLEMENTATION ***************/

var TEMPLATE_CONFIG = {
  // The name of the file that will be created on Drive to store data
  // between executions of the script. You must use a different
  // filename for each each script running in the account, or data
  // from different scripts may overwrite one another.
  FILENAME: 'UNIQUE_FILENAME_HERE',

  // The minimum number of days between the start of each cycle.
  MIN_FREQUENCY: 1,

  // Controls whether child accounts will be processed in parallel (true)
  // or sequentially (false).
  USE_PARALLEL_MODE: true,

  // Controls the maximum number of accounts that will be processed in a
  // single script execution.
  MAX_ACCOUNTS: 50,

  // A list of ManagedAccountSelector conditions to restrict the population
  // of child accounts that will be processed. Leave blank or comment out
  // to include all child accounts.
  ACCOUNT_CONDITIONS: []
};

// The possible statuses for the script as a whole or an individual account.
var Statuses = {
  NOT_STARTED: 'Not Started',
  STARTED: 'Started',
  FAILED: 'Failed',
  COMPLETE: 'Complete'
};

/**
 * Your main logic for initializing a cycle for your script.
 *
 * @param {Array.<string>} customerIds The customerIds that this cycle
 *     will process.
 */
function initializeCycle(customerIds) {
  // REPLACE WITH YOUR IMPLEMENTATION

  // This example simply prints the accounts that will be process in
  // this cycle.
  Logger.log('Accounts to be processed this cycle:');
  for (var i = 0; i < customerIds.length; i++) {
    Logger.log(customerIds[i]);
  }
}

/**
 * Your main logic for initializing a single execution of the script.
 *
 * @param {Array.<string>} customerIds The customerIds that this
 *     execution will process.
 */
function initializeExecution(customerIds) {
  // REPLACE WITH YOUR IMPLEMENTATION

  // This example simply prints the accounts that will be process in
  // this execution.
  Logger.log('Accounts to be processed this execution:');
  for (var i = 0; i < customerIds.length; i++) {
    Logger.log(customerIds[i]);
  }
}

/**
 * Your main logic for processing a single AdWords account. This function
 * can perform any sort of processing on the account, followed by
 * outputting results immediately (e.g., sending an email, saving to a
 * spreadsheet, etc.) and/or returning results to be output later, e.g.,
 * to be combined with the output from other accounts.
 *
 * @return {Object} An object containing any results of your processing
 *    that you want to output later.
 */
function processAccount() {
  // REPLACE WITH YOUR IMPLEMENTATION

  // This example simply returns the number of campaigns and ad groups
  // in the account.
  return {
    numCampaigns: AdWordsApp.campaigns().get().totalNumEntities(),
    numAdGroups: AdWordsApp.adGroups().get().totalNumEntities()
  };
}

/**
 * Your main logic for consolidating or outputting results after
 * a single execution of the script. These single execution results may
 * reflect the processing on only a subset of your accounts.
 *
 * @param {Object.<string, {
 *       status: string,
 *       returnValue: Object,
 *       error: string
 *     }>} results The results for the accounts processed in this
 *    execution of the script, keyed by customerId. The status will be
 *    Statuses.COMPLETE if the account was processed successfully,
 *    Statuses.FAILED if there was an error, and Statuses.STARTED if it
 *    timed out. The returnValue field is present when the status is
 *    Statuses.COMPLETE and corresponds to the object you returned in
 *    processAccount(). The error field is present when the status is
 *    Statuses.FAILED.
 */
function processIntermediateResults(results) {
  // REPLACE WITH YOUR IMPLEMENTATION

  // This example simply logs the number of campaigns and ad groups
  // in each of the accounts successfully processed in this execution.
  Logger.log('Results of this execution:');
  for (var customerId in results) {
    var result = results[customerId];
    if (result.status == Statuses.COMPLETE) {
      Logger.log(customerId + ': ' + result.returnValue.numCampaigns +
                 ' campaigns, ' + result.returnValue.numAdGroups +
                 ' ad groups');
    } else if (result.status == Statuses.STARTED) {
      Logger.log(customerId + ': timed out');
    } else {
      Logger.log(customerId + ': failed due to "' + result.error + '"');
    }
  }
}

/**
 * Your main logic for consolidating or outputting results after
 * the script has executed a complete cycle across all of your accounts.
 * This function will only be called once per complete cycle.
 *
 * @param {Object.<string, {
 *       status: string,
 *       returnValue: Object,
 *       error: string
 *     }>} results The results for the accounts processed in this
 *    execution of the script, keyed by customerId. The status will be
 *    Statuses.COMPLETE if the account was processed successfully,
 *    Statuses.FAILED if there was an error, and Statuses.STARTED if it
 *    timed out. The returnValue field is present when the status is
 *    Statuses.COMPLETE and corresponds to the object you returned in
 *    processAccount(). The error field is present when the status is
 *    Statuses.FAILED.
 */
function processFinalResults(results) {
  // REPLACE WITH YOUR IMPLEMENTATION

  // This template simply logs the total number of campaigns and ad
  // groups across all accounts successfully processed in the cycle.
  var numCampaigns = 0;
  var numAdGroups = 0;

  Logger.log('Results of this cycle:');
  for (var customerId in results) {
    var result = results[customerId];
    if (result.status == Statuses.COMPLETE) {
      Logger.log(customerId + ': successful');
      numCampaigns += result.returnValue.numCampaigns;
      numAdGroups += result.returnValue.numAdGroups;
    } else if (result.status == Statuses.STARTED) {
      Logger.log(customerId + ': timed out');
    } else {
      Logger.log(customerId + ': failed due to "' + result.error + '"');
    }
  }

  Logger.log('Total number of campaigns: ' + numCampaigns);
  Logger.log('Total number of ad groups: ' + numAdGroups);
}

/**************** END OF YOUR IMPLEMENTATION ****************/

/**************** START OF STANDARD TEMPLATE ****************/

// Whether or not the script is running in a manager account.
var IS_MANAGER = typeof MccApp !== 'undefined';

// The maximum number of accounts that can be processed when using
// executeInParallel().
var MAX_PARALLEL = 50;

// The possible modes in which the script can execute.
var Modes = {
  SINGLE: 'Single',
  MANAGER_SEQUENTIAL: 'Manager Sequential',
  MANAGER_PARALLEL: 'Manager Parallel'
};

function main() {
  var mode = getMode();
  stateManager.loadState();

  // The last execution may have attempted the final set of accounts but
  // failed to actually complete the cycle because of a timeout in
  // processIntermediateResults(). In that case, complete the cycle now.
  if (stateManager.getAccountsWithStatus().length > 0) {
    completeCycleIfNecessary();
  }

  // If the cycle is complete and enough time has passed since the start of
  // the last cycle, reset it to begin a new cycle.
  if (stateManager.getStatus() == Statuses.COMPLETE) {
    if (dayDifference(stateManager.getLastStartTime(), new Date()) >
        TEMPLATE_CONFIG.MIN_FREQUENCY) {
      stateManager.resetState();
    } else {
      Logger.log('Waiting until ' + TEMPLATE_CONFIG.MIN_FREQUENCY +
                 ' days have elapsed since the start of the last cycle.');
      return;
    }
  }

  // Find accounts that have not yet been processed. If this is the
  // beginning of a new cycle, this will be all accounts.
  var customerIds =
      stateManager.getAccountsWithStatus(Statuses.NOT_STARTED);

  // The status will be Statuses.NOT_STARTED if this is the very first
  // execution or if the cycle was just reset. In either case, it is the
  // beginning of a new cycle.
  if (stateManager.getStatus() == Statuses.NOT_STARTED) {
    stateManager.setStatus(Statuses.STARTED);
    stateManager.saveState();

    initializeCycle(customerIds);
  }

  // Don't attempt to process more accounts than specified, and
  // enforce the limit on parallel execution if necessary.
  var accountLimit = TEMPLATE_CONFIG.MAX_ACCOUNTS;

  if (mode == Modes.MANAGER_PARALLEL) {
    accountLimit = Math.min(MAX_PARALLEL, accountLimit);
  }

  var customerIdsToProcess = customerIds.slice(0, accountLimit);

  // Save state so that we can detect when an account timed out by it still
  // being in the STARTED state.
  stateManager.setAccountsWithStatus(customerIdsToProcess, Statuses.STARTED);
  stateManager.saveState();

  initializeExecution(customerIdsToProcess);
  executeByMode(mode, customerIdsToProcess);
}

/**
 * Runs the script on a list of accounts in a given mode.
 *
 * @param {string} mode The mode the script should run in.
 * @param {Array.<string>} customerIds The customerIds that this execution
 *     should process. If mode is Modes.SINGLE, customerIds must contain
 *     a single element which is the customerId of the AdWords account.
 */
function executeByMode(mode, customerIds) {
  switch (mode) {
    case Modes.SINGLE:
      var results = {};
      results[customerIds[0]] = tryProcessAccount();
      completeExecution(results);
      break;

    case Modes.MANAGER_SEQUENTIAL:
      var accounts = MccApp.accounts().withIds(customerIds).get();
      var results = {};

      var managerAccount = AdWordsApp.currentAccount();
      while (accounts.hasNext()) {
        var account = accounts.next();
        MccApp.select(account);
        results[account.getCustomerId()] = tryProcessAccount();
      }
      MccApp.select(managerAccount);

      completeExecution(results);
      break;

    case Modes.MANAGER_PARALLEL:
      if (customerIds.length == 0) {
        completeExecution({});
      } else {
        var accountSelector = MccApp.accounts().withIds(customerIds);
        accountSelector.executeInParallel('parallelFunction',
                                          'parallelCallback');
      }
      break;
  }
}

/**
 * Attempts to process the current AdWords account.
 *
 * @return {Object} The result of the processing if successful, or
 *     an object with status Statuses.FAILED and the error message
 *     if unsuccessful.
 */
function tryProcessAccount() {
  try {
    return {
      status: Statuses.COMPLETE,
      returnValue: processAccount()
    };
  } catch (e) {
    return {
      status: Statuses.FAILED,
      error: e.message
    };
  }
}

/**
 * The function given to executeInParallel() when running in parallel mode.
 * This helper function is necessary so that the return value of
 * processAccount() is transformed into a string as required by
 * executeInParallel().
 *
 * @return {string} JSON string representing the return value of
 *     processAccount().
 */
function parallelFunction() {
  var returnValue = processAccount();
  return JSON.stringify(returnValue);
}

/**
 * The callback given to executeInParallel() when running in parallel mode.
 * Processes the execution results into the format used by all execution
 * modes.
 *
 * @param {Array.<Object>} executionResults An array of execution results
 *     from a parallel execution.
 */
function parallelCallback(executionResults) {
  var results = {};

  for (var i = 0; i < executionResults.length; i++) {
    var executionResult = executionResults[i];
    var status;

    if (executionResult.getStatus() == 'OK') {
      status = Statuses.COMPLETE;
    } else if (executionResult.getStatus() == 'TIMEOUT') {
      status = Statuses.STARTED;
    } else {
      status = Statuses.FAILED;
    }

    results[executionResult.getCustomerId()] = {
      status: status,
      returnValue: JSON.parse(executionResult.getReturnValue()),
      error: executionResult.getError()
    };
  }

  // After executeInParallel(), variables in global scope are reevaluated,
  // so reload the state.
  stateManager.loadState();

  completeExecution(results);
}

/**
 * Completes a single execution of the script by saving the results and
 * calling the intermediate and final result handlers as necessary.
 *
 * @param {Object.<string, {
 *       status: string,
 *       returnValue: Object,
 *       error: string
 *     }>} results The results of the current execution of the script.
 */
function completeExecution(results) {
  for (var customerId in results) {
    var result = results[customerId];
    stateManager.setAccountWithResult(customerId, result);
  }
  stateManager.saveState();

  processIntermediateResults(results);
  completeCycleIfNecessary();
}

/**
 * Completes a full cycle of the script if all accounts have been attempted
 * but the cycle has not been marked as complete yet.
 */
function completeCycleIfNecessary() {
  if (stateManager.getAccountsWithStatus(Statuses.NOT_STARTED).length == 0 &&
      stateManager.getStatus() != Statuses.COMPLETE) {
    stateManager.setStatus(Statuses.COMPLETE);
    stateManager.saveState();
    processFinalResults(stateManager.getResults());
  }
}

/**
 * Determines what mode the script should run in.
 *
 * @return {string} The mode to run in.
 */
function getMode() {
  if (IS_MANAGER) {
    if (TEMPLATE_CONFIG.USE_PARALLEL_MODE) {
      return Modes.MANAGER_PARALLEL;
    } else {
      return Modes.MANAGER_SEQUENTIAL;
    }
  } else {
    return Modes.SINGLE;
  }
}

/**
 * Finds all customer IDs that the script could process. For a single account,
 * this is simply the account itself.
 *
 * @return {Array.<string>} A list of customer IDs.
 */
function getCustomerIdsPopulation() {
  if (IS_MANAGER) {
    var customerIds = [];

    var selector = MccApp.accounts();
    var conditions = TEMPLATE_CONFIG.ACCOUNT_CONDITIONS || [];
    for (var i = 0; i < conditions.length; i++) {
      selector = selector.withCondition(conditions[i]);
    }

    var accounts = selector.get();
    while (accounts.hasNext()) {
      customerIds.push(accounts.next().getCustomerId());
    }

    return customerIds;
  } else {
    return [AdWordsApp.currentAccount().getCustomerId()];
  }
}

/**
 * Returns the number of days between two dates.
 *
 * @param {Object} from The older Date object.
 * @param {Object} to The newer (more recent) Date object.
 * @return {number} The number of days between the given dates (possibly
 *     fractional).
 */
function dayDifference(from, to) {
  return (to.getTime() - from.getTime()) / (24 * 3600 * 1000);
}

/**
 * Loads a JavaScript object previously saved as JSON to a file on Drive.
 *
 * @param {string} filename The name of the file in the account's root Drive
 *     folder where the object was previously saved.
 * @return {Object} The JavaScript object, or null if the file was not found.
 */
function loadObject(filename) {
  var files = DriveApp.getRootFolder().getFilesByName(filename);

  if (!files.hasNext()) {
    return null;
  } else {
    var file = files.next();

    if (files.hasNext()) {
      throwDuplicateFileException(filename);
    }

    return JSON.parse(file.getBlob().getDataAsString());
  }
}

/**
 * Saves a JavaScript object as JSON to a file on Drive. An existing file with
 * the same name is overwritten.
 *
 * @param {string} filename The name of the file in the account's root Drive
 *     folder where the object should be saved.
 * @param {obj} obj The object to save.
 */
function saveObject(filename, obj) {
  var files = DriveApp.getRootFolder().getFilesByName(filename);

  if (!files.hasNext()) {
    DriveApp.createFile(filename, JSON.stringify(obj));
  } else {
    var file = files.next();

    if (files.hasNext()) {
      throwDuplicateFileException(filename);
    }

    file.setContent(JSON.stringify(obj));
  }
}

/**
 * Throws an exception if there are multiple files with the same name.
 *
 * @param {string} filename The filename that caused the error.
 */
function throwDuplicateFileException(filename) {
  throw 'Multiple files named ' + filename + ' detected. Please ensure ' +
      'there is only one file named ' + filename + ' and try again.';
}

var stateManager = (function() {
  /**
   * @type {{
   *   cycle: {
   *     status: string,
   *     lastUpdate: string,
   *     startTime: string
   *   },
   *   accounts: Object.<string, {
   *     status: string,
   *     lastUpdate: string,
   *     returnValue: Object
   *   }>
   * }}
   */
  var state;

  /**
   * Loads the saved state of the script. If there is no previously
   * saved state, sets the state to an initial default.
   */
  var loadState = function() {
    state = loadObject(TEMPLATE_CONFIG.FILENAME);
    if (!state) {
      resetState();
    }
  };

  /**
   * Saves the state of the script to Drive.
   */
  var saveState = function() {
    saveObject(TEMPLATE_CONFIG.FILENAME, state);
  };

  /**
   * Resets the state to an initial default.
   */
  var resetState = function() {
    state = {};
    var date = Date();

    state.cycle = {
      status: Statuses.NOT_STARTED,
      lastUpdate: date,
      startTime: date
    };

    state.accounts = {};
    var customerIds = getCustomerIdsPopulation();

    for (var i = 0; i < customerIds.length; i++) {
      state.accounts[customerIds[i]] = {
        status: Statuses.NOT_STARTED,
        lastUpdate: date
      };
    }
  };

  /**
   * Gets the status of the current cycle.
   *
   * @return {string} The status of the current cycle.
   */
  var getStatus = function() {
    return state.cycle.status;
  };

  /**
   * Sets the status of the current cycle.
   *
   * @param {string} status The status of the current cycle.
   */
  var setStatus = function(status) {
    var date = Date();

    if (status == Statuses.IN_PROGRESS &&
        state.cycle.status == Statuses.NOT_STARTED) {
      state.cycle.startTime = date;
    }

    state.cycle.status = status;
    state.cycle.lastUpdate = date;
  };

  /**
   * Gets the start time of the current cycle.
   *
   * @return {Object} Date object for the start of the last cycle.
   */
  var getLastStartTime = function() {
    return new Date(state.cycle.startTime);
  };

  /**
   * Gets accounts in the current cycle with a particular status.
   *
   * @param {string} status The status of the accounts to get.
   *     If null, all accounts are retrieved.
   * @return {Array.<string>} A list of matching customerIds.
   */
  var getAccountsWithStatus = function(status) {
    var customerIds = [];

    for (var customerId in state.accounts) {
      if (!status || state.accounts[customerId].status == status) {
        customerIds.push(customerId);
      }
    }

    return customerIds;
  };

  /**
   * Sets accounts in the current cycle with a particular status.
   *
   * @param {Array.<string>} customerIds A list of customerIds.
   * @param {string} status A status to apply to those customerIds.
   */
  var setAccountsWithStatus = function(customerIds, status) {
    var date = Date();

    for (var i = 0; i < customerIds.length; i++) {
      var customerId = customerIds[i];

      if (state.accounts[customerId]) {
        state.accounts[customerId].status = status;
        state.accounts[customerId].lastUpdate = date;
      }
    }
  };

  /**
   * Registers the processing of a particular account with a result.
   *
   * @param {string} customerId The account that was processed.
   * @param {{
   *       status: string,
   *       returnValue: Object
   *       error: string
   *     }} result The object to save for that account.
   */
  var setAccountWithResult = function(customerId, result) {
    if (state.accounts[customerId]) {
      state.accounts[customerId].status = result.status;
      state.accounts[customerId].returnValue = result.returnValue;
      state.accounts[customerId].error = result.error;
      state.accounts[customerId].lastUpdate = Date();
    }
  };

  /**
   * Gets the current results of the cycle for all accounts.
   *
   * @return {Object.<string, {
   *       status: string,
   *       lastUpdate: string,
   *       returnValue: Object,
   *       error: string
   *     }>} The results processed by the script during the cycle,
   *    keyed by account.
   */
  var getResults = function() {
    return state.accounts;
  };

  return {
    loadState: loadState,
    saveState: saveState,
    resetState: resetState,
    getStatus: getStatus,
    setStatus: setStatus,
    getLastStartTime: getLastStartTime,
    getAccountsWithStatus: getAccountsWithStatus,
    setAccountsWithStatus: setAccountsWithStatus,
    setAccountWithResult: setAccountWithResult,
    getResults: getResults
  };
})();

/***************** END OF STANDARD TEMPLATE *****************/

Feedback geben zu...

AdWords Scripts
AdWords Scripts