大規模管理者の階層型テンプレート

クライアント センター(MCC)アカウントを使って、何百、何千という単位の子アカウントを管理できますが、そのすべてのアカウントに対してスクリプトを実行する場合、制限が多くなります。たとえば、executeInParallel を使用して並列処理でスクリプトを実行する場合、1 回の実行で処理できる最大アカウント数は 50 に制限されます。また、反復処理を順番に行う場合、アカウント数が多すぎると、スクリプトがタイムアウトになることがあります。

このような問題に対しては、大規模管理者の階層型テンプレートのスクリプトを使用し、スクリプトを実行するたびに処理するアカウントを変えることで対応できます。スクリプトを実行すると、その都度 Google ドライブ上の一時ファイルに中間結果が保存され、その後にスクリプトを実行する際に、未処理のアカウントがどれかがわかるようになります。スクリプトの実行を繰り返すことで、最終的にすべての子アカウントの処理が完了します。最後のアカウントの処理が完了すると、スクリプトはリセットされ、新しい処理サイクルが開始します(実行頻度はお客様の指定に基づきます)。

テンプレートは 2 つの部分で構成されています。「STANDARD TEMPLATE」という部分では、スクリプトの実行を繰り返して、すべてのアカウントを処理するように設定します。お客様が編集を加える必要はありません。「YOUR IMPLEMENTATION」という部分には、「フック」と呼ばれる複数のプレースホルダ関数が含まれており、ご自身のアカウントに対して実際に適用する処理ロジックを入力します。下記のソースコードは、説明のための実装例です。

このページでは、「実行」という語はスクリプトを 1 回実行することを意味します。また、「サイクル」という語は、スクリプトの実行を繰り返して、最終的にすべてのアカウントを処理が完了することを意味します。

スクリプトの設定

スクリプトの設定は、テンプレートを使用して、次の 3 つの手順で行います。

  1. テンプレートを使用した新しいスクリプトの作成
  2. テンプレート単位のパラメータの設定
  3. 適用したいロジックの入力

テンプレートを使用した新しいスクリプトの作成

下記のソースコードを使って新しいスクリプトを設定します。TEMPLATE_CONFIG の欄に FILENAME を入力します。サイクルの中間結果を Google ドライブに保存するために使用するファイルの名前です。スクリプトは、最初の実行時にこのファイルを作成し、その後の実行時に再利用します。FILENAME には好きな名前を設定できますが、スクリプトの名前に近いものを設定することをおすすめします。こうすることで、後で必要が生じたときにファイルを見つけやすくなります。

FILENAME は、アカウント内でこのテンプレートを使用するすべてのスクリプトに対して、一意の名前にする必要があります。一意の名前でない場合、他のスクリプトから生成された中間結果データで上書きされるおそれがあります。また、FILENAME は、Google ドライブですでに所有している他のファイルと重複していない必要があります。

テンプレート単位のパラメータの設定

テンプレート単位の複数のパラメータを設定する必要があります。

  • MIN_FREQUENCY: サイクルの最低頻度を調整します。次のサイクルが開始するのは、前のサイクルが開始した時点から、最低でもこのパラメータで設定した日数が経過した後です。サイクルの完了までに MIN_FREQUENCY の設定値以上の時間がかかった場合、実際の頻度が設定よりも長くなる場合があります。これは、処理するアカウントの数が膨大である場合や、スクリプトのスケジュールに設定されている頻度が低すぎて MIN_FREQUENCY の設定日数内にすべてのアカウントを処理できない場合などに起こります。
  • USE_PARALLEL_MODE: スクリプトがアカウントを並列処理するか、順番に反復処理するかを設定します。
  • MAX_ACCOUNTS: スクリプトが 1 回の実行で処理を試みるアカウントの最大数を調整します。並列処理の場合、処理するアカウントの数は 50 が上限となります。
  • ACCOUNT_CONDITIONS(省略可): どのアカウントをスクリプトで処理するかを調整する、一連の ManagedAccountSelector 条件です。たとえば、特定のラベルが設定されたアカウントのみ処理するよう、「LabelNames CONTAINS “ACCOUNT_LABEL”」という条件を追加できます。

これらのパラメータを状況に応じて設定するためのヒントは、下記の「スケジュール設定」をご覧ください。

適用したいロジックの入力

テンプレートは、お客様がご自身のアカウントに対して実際に適用する独自の処理ロジックを実装できるようになっています。ロジックを実装するために、テンプレートには 5 つの関数(「フック」)が用意されており、各サイクルの特定のタイミングで呼び出されます(以下の図を参照)。各フックがスクリプトに必要な処理を行えるよう、処理ロジックを実装できます。

テンプレートには、各フックの設定例が含まれています。設定例に当たる部分はお客様ご自身のロジックで置き換えてください。また、通常と同じようにご自身の関数や変数をスクリプトに追加して、この 5 つのフック内から使用できます。

initializeCycle(customerIds)
この関数は、各サイクルの最初に 1 回、処理するアカウントのリスト(ACCOUNT_CONDITIONS に合致したもの)と一緒に呼び出されます。この関数を使って、サイクル全体に対して必要な初期化を行うことができます。想定される使用例は以下のとおりです。
  • サイクル開始を通知するメールを送信する。
  • サイクルの間、スクリプトの全実行で統一したい静的データの読み込みや生成を行う。データを後の各実行で読み込めるようにするには、Google ドライブやスプレッドシート内などに保存する必要があります。
初期化の必要がなければ、この設定は空白のままにします。
initializeExecution(customerIds)
この関数は、各実行の最初に 1 回、実行中に処理されるアカウントと一緒に呼び出されます。この関数を使って、実行の都度、必要な初期化を行うことができます。想定される使用例は以下のとおりです。
  • 実行開始を通知するメールを送信する。
  • すべての実行で統一したい静的データを読み込む(上記参照)。
初期化の必要がなければ、この設定は空白のままにします。
processAccount()
この関数は、1 回のサイクルの中で、各アカウントに対して 1 回呼び出されます。この関数を使って、レポートの取得や分析、キャンペーンの一時停止や再開、入札単価の変更、広告の作成、メールアラートの送信、スプレッドシートへのデータ保存など、AdWords スクリプトを使用して行うことができる主な処理ロジックを実行できます。また、この関数はアカウントの処理に関連する任意の結果を、必要に応じて返すことができます。結果が返されることで、複数のアカウントの結果を出力前に統合できるようになります。たとえば、アカウントごとに結果を 1 通ずつメールで送信するのではなく、すべてのアカウントの結果を 1 通のメールアラートで送信できるようになります(下記参照)。
processIntermediateResults(results)
この関数は、各実行の最後に 1 回、実行中に処理したアカウントの結果と一緒に呼び出されます。この関数を使用して、各実行の結果の分析、統合、出力を行うことができます。想定される使用例は以下のとおりです。
  • 各実行の結果をスプレッドシートに追加する。
  • 各実行の全結果を通知する概要メールを作成し、送信する。
出力したい結果がない場合や、サイクル全体が完了した後にのみ結果を出力したい場合は、この設定は空白のままにします。
processFinalResults(results)
この関数は、各サイクルの最後に 1 回、サイクル中に処理した全アカウントの結果と一緒に呼び出されます。この関数を使用して、サイクルに含まれるすべてのアカウントから得た結果の分析、統合、出力を行うことができます。想定される使用例は以下のとおりです。
  • すべてのアカウントの結果が必要な分析を行う。
  • すべての結果をスプレッドシートに 1 度に追加する。
  • 全アカウントの結果を通知する概要メールを作成し、送信する。
出力したい結果がない場合、この設定は空白のままにします。

スケジュール設定

MCC アカウント用のテンプレートを使用してスクリプトのスケジュールを設定する際には、個別の実行ではなく、サイクル全体を考慮しなければならないため、他のスクリプトの場合よりも慎重な検討が必要になります。ほとんどのお客様は、各サイクルをできるだけ早く完了させたいとお考えです。このためには、次の手順に沿って設定します。

  • スクリプトが毎時間実行されるようスケジュールを設定します。これは最も頻度が高い設定です。
  • 1 つのアカウントで processAccount() 関数の実行に必要な時間を推定します。推定方法の 1 つは、ACCOUNT_CONDITIONS を設定し、反復処理を順番に行うようにスクリプトを実行して、少数のアカウントだけを処理した後に、その処理にかかった時間をアカウント数で割る方法です。
  • USE_PARALLEL_MODEMAX_ACCOUNTS を次のとおりに設定します。
    • 反復処理を順番に行う場合、スクリプトをタイムアウトまでに実行できる時間は 30 分間(1,800 秒間)です。並列処理でスクリプトを実行する場合は、最大 50 アカウントまで処理できます。1 回の実行で処理できるアカウントが多いのはどちらの方法になるかは、スクリプトが各アカウントの処理にかかる時間によって変わります。
    • アカウントあたりの推定処理時間が 36 秒(1,800 秒÷ 50 アカウント)未満の場合、反復処理を順番に行うことをおすすめします。同様に、アカウントあたりの推定処理時間が 36 秒以上の場合は、並列処理をおすすめします。アカウントあたりの処理時間のばらつきを考慮し、また追加の後処理にも余裕を持たせるには、36 秒ではなく 30 秒を基準とすることが適切です。
    • 並列処理にする場合、MAX_ACCOUNTS を 50 に設定します。上限が 50 なので、それを超える数値を指定しても意味がありません。
    • 反復処理にする場合、MAX_ACCOUNTS を 30 分以内に処理できる数に設定します。たとえば、各アカウントを 10 秒で処理できると推定した場合、MAX_ACCOUNTS を 180 に設定します。実際は、設定をやや小さめの数値にして、processIntermediateResults()processFinalResults() の関数をできるようにすることをおすすめします。

各実行のスケジュールを毎時間に設定した場合でも、MIN_FREQUENCY を設定してサイクルの間隔を調整できます。たとえば週に 1 回、全アカウントを対象として、スプレッドシート レポートを作成したい場合、processFinalResults() フックでスプレッドシートを作成し、MIN_FREQUENCY を 7 に設定します。

仕組み

テンプレートの中心となるのは、「STANDARD TEMPLATE」という部分です。ここには、スクリプトの main() 関数が含まれています。stateManager オブジェクトは、前回までの実行からサイクルのステータスを読み込んで保存するためのメソッドを示します。

各実行のスタート時に、main() は前回のステータスを読み込み、最後に完了したサイクルから十分な時間が経過していれば新しいサイクルを開始します。そして、処理対象となるアカウントを決定し、initializeCycle() フック(必要な場合のみ)と initializeExecution() フックを呼び出します。次に executeByMode() を呼び出します。これは反復処理か並列処理によってアカウントを処理します。どちらの方法でも、処理対象の各アカウントに対して processAccount() フックが呼び出され、最終的に completeExecution() に結果が渡され、ドライブに保存されます。最後に、テンプレートは processIntermediateResults() フックを呼び出します。また、サイクルの最後であれば、processFinalResults フックも呼び出します。

このテンプレートは、MCC アカウントでの使用を想定したものですが、単一アカウントでも問題なく使用できます。適用したいロジックの開発やデバッグを行う際は、このテンプレートを単一アカウントで使用すると便利です。また、MCC アカウントで使用したことのあるスクリプトを、FILENAME 以外は変更を加えずに、単一アカウントに流用することもできます。

設定方法

  • 下記のソースコードを使ってスクリプトを作成します。
  • スクリプトの設定に記載されている手順に沿って設定します。

ソースコード

// 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 *****************/

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。