Modelo de hierarquia grande de administrador

Uma conta de administrador do Google AdWords pode gerenciar centenas ou até mesmo milhares de contas de clientes. Executar um script em todas elas pode ser desafiador, já que trabalhar nelas em paralelo usando executeInParallel limita seu script a um máximo de 50 contas por execução. Fazer iterações nelas em sequência pode ocasionar erro de tempo limite se você tiver muitas contas.

Para lidar com esse desafio, o script Modelo de hierarquia maior de administrador processa um subconjunto diferente de contas a cada execução. Cada execução armazena os resultados intermediários em um arquivo temporário no Google Drive. Assim, as execuções subsequentes sabem quais contas ainda precisam ser processadas. No decorrer de várias execuções, o script eventualmente processará todas as contas de cliente. Depois de terminar as últimas contas, ele é redefinido e inicia um novo ciclo de processamento (sujeito a uma frequência que você especifica).

O modelo é dividido em duas seções. A seção chamada STANDARD TEMPLATE fornece a funcionalidade de passar por todas as suas contas em várias execuções do script, sem a necessidade de você editá-lo. A seção chamada YOUR IMPLEMENTATION fornece várias funções de marcador de posição ou "ganchos" para você preencher com a lógica de processamento que realmente deseja realizar nas suas contas. O código-fonte abaixo mostra um exemplo ilustrativo de implementação.

Nesta página inteira, o termo execução refere-se a uma única execução do script, enquanto o termo ciclo refere-se ao script que processa todas as suas contas no decorrer de várias execuções individuais.

Configuração do script

O uso do modelo consiste em três etapas:

  1. Criar um novo script a partir do modelo.
  2. Definir os parâmetros no nível do modelo.
  3. Fornecer sua lógica fundamental.

Criar um novo script a partir do modelo

Configure um novo script com o código-fonte abaixo. No objeto TEMPLATE_CONFIG, forneça um FILENAME para o arquivo que será usado para salvar os resultados intermediários no Drive. O script criará esse arquivo na primeira execução e o reutilizará nas execuções subsequentes. O FILENAME pode ser qualquer um, mas é prática recomendada atribuir um nome semelhante ao do script. Assim será mais fácil identificar o arquivo posteriormente, se for necessário.

O FILENAME precisa ser único entre todos os scripts que usam esse modelo na sua conta. Caso contrário, os dados intermediários dos diferentes scripts se modificarão entre si. O FILENAME também não deve ser igual a outro arquivo que já esteja na conta do Drive.

Definir os parâmetros no nível do modelo

Há vários parâmetros no nível do modelo a serem definidos.

  • MIN_FREQUENCY: controla a frequência mínima dos ciclos. O próximo ciclo não começará pelo menos tantos dias depois do início do ciclo anterior. A frequência real pode ser maior se o próprio ciclo demorar mais do que a MIN_FREQUENCY para ser concluído. Isso pode ocorrer, por exemplo, quando houver um número maior de contas a serem processadas ou o próprio script for programado com pouca frequência de maneira que não passa por todas elas no número de dias da MIN_FREQUENCY.
  • USE_PARALLEL_MODE: controla se o script processa contas em paralelo ou faz iterações sequencialmente nas contas.
  • MAX_ACCOUNTS: controla o número máximo de contas que o script tentará processar em uma única execução. No modo paralelo, o limite do número real de contas processadas é 50.
  • ACCOUNT_CONDITIONS (opcional): uma matriz de condições ManagedAccountSelector para controlar as contas que são processadas pelo script. Por exemplo, você pode adicionar uma condição como 'LabelNames CONTAINS "ACCOUNT_LABEL"' para processar apenas contas com um determinado rótulo.

Consulte Programação abaixo para sugestões sobre como definir esses parâmetros para diferentes casos de uso.

Fornecer sua lógica fundamental

O modelo não seria útil a menos que tivesse um lugar para você implementar sua própria lógica com o processamento real que deseja executar nas suas contas. Para isso, o modelo oferece cinco funções ou "ganchos", que são chamados em momentos específicos durante cada ciclo, conforme ilustrado abaixo. É possível fornecer uma implementação para cada gancho executar o processamento que você precisa para seu script.

Para cada gancho, o modelo inclui um exemplo de implementação que deve ser substituído pela sua própria lógica. Você também pode adicionar suas próprias funções e variáveis ao seu script, como faria normalmente, e usá-las a partir desses cinco ganchos.

initializeCycle(customerIds)
Esta função é chamada uma vez no início de cada ciclo com a lista de contas que serão processadas (aqueles que correspondem a ACCOUNT_CONDITIONS). Use-a para executar qualquer inicialização que seu script precise em um ciclo completo. Veja alguns casos de uso possíveis:
  • Enviar um e-mail para indicar que um ciclo foi iniciado.
  • Carregar ou gerar dados estáticos que você deseja que sejam iguais para todas as execuções do seu script durante o ciclo. Você precisa salvar os dados no Drive ou em uma planilha para que possam ser carregados posteriormente em cada execução.
Se você não tiver uma inicialização para executar, deixe-a em branco.
initializeExecution(customerIds)
Essa função é chamada uma vez no início de cada execução com o subconjunto de contas que serão processadas durante a execução. Use-a para executar qualquer inicialização que seu script precise em cada execução. Veja alguns casos de uso possíveis:
  • Enviar um e-mail para indicar que a execução foi iniciada.
  • Carregar dados estáticos que deveriam ser iguais para todas as execuções (veja acima).
Se você não tiver uma inicialização para executar, deixe-a em branco.
processAccount()
Em um ciclo, essa função é chamada uma vez em cada conta do Google AdWords. Use-a para executar a lógica principal de processamento do seu script, como recuperar e analisar relatórios, pausar/retomar campanhas, modificar lances, criar anúncios, enviar um alerta de e-mail, salvar dados em uma planilha ou qualquer outra ação possível com scripts do Google AdWords. Como alternativa, essa função pode retornar resultados arbitrários associados ao processamento da conta. Com o retorno de resultados, você pode consolidar resultados em todas as contas antes de enviá-los, como por meio de um alerta de e-mail único que mostra os resultados de todas as contas, em vez de um e-mail por conta (veja abaixo).
processIntermediateResults(results)
Essa função é chamada uma vez no final de cada execução, com os resultados das contas processadas durante a execução em questão. Use-a para analisar, consolidar e gerar os relatórios da execução. Veja alguns casos de uso possíveis:
  • Adicionar os resultados dessa execução a uma planilha.
  • Criar e enviar um e-mail de resumo com todos os resultados dessa execução.
Se você não tiver nenhum resultado que queira gerar ou se quiser gerá-los apenas depois do término do ciclo inteiro, deixe sua implementação em branco.
processFinalResults(results)
Essa função é chamada uma vez no final de cada ciclo, com os resultados de todas as contas processadas durante o ciclo. Use-a para analisar, consolidar e gerar os resultados de todas as contas que você incluiu no ciclo. Veja alguns casos de uso possíveis:
  • Executar uma análise que necessita dos resultados de todas as suas contas.
  • Adicionar todos os resultados a uma planilha de uma vez.
  • Criar e enviar um e-mail de resumo com todos os resultados em todas as contas.
Se você não quiser gerar nenhum resultado, deixe sua implementação em branco.

Programação

Programar um script que usa o modelo de conta de administrador requer mais cuidados do que os demais scripts, porque você precisa pensar em termos de ciclos, e não de execuções individuais. A maioria dos usuários quer que os ciclos sejam concluídos o mais rápido possível. Para fazer isso, siga estas etapas:

  • Programe a execução do script no Google AdWords a cada hora, que é a configuração mais frequente.
  • Estime quanto tempo a implementação processAccount() demora em uma única conta. Uma maneira de estimar isso é executando o script no modo sequencial com ACCOUNT_CONDITIONS configurado para funcionar apenas em algumas contas, dividindo o tempo de execução pelo número de contas.
  • Defina USE_PARALLEL_MODE e MAX_ACCOUNTS desta forma.
    • Um script em execução no modo sequencial pode ser executado por 30 minutos (1.800 segundos) antes de atingir o tempo limite. Um script em execução no modo paralelo pode operar em até 50 contas. O modo que processa mais contas por execução depende de quanto tempo o script leva para processar cada conta.
    • Se você estima que seu tempo de processamento por conta é inferior a 36 segundos (1.800 / 50), é preferível utilizar o modo sequencial. Da mesma forma, se você estima que seu processamento leva mais que 36 segundos por conta, é preferível utilizar o modo paralelo. É razoável usar um corte de 30, em vez de 36, para levar em consideração as variações no tempo de processamento por conta, além de alguma folga para pós-processamentos adicionais.
    • Se você estiver usando o modo paralelo, defina MAX_ACCOUNTS como 50. Como o limite é 50, não há nenhum benefício em especificar um número maior.
    • Se estiver usando o modo sequencial, defina MAX_ACCOUNTS como o maior número que você consegue processar em 30 minutos. Por exemplo, se você estima que cada conta pode ser processada em 10 segundos, defina MAX_ACCOUNTS como 180. Na prática, um número ligeiramente menor é melhor, pois deixa tempo para que as implementações processIntermediateResults() e processFinalResults() sejam executadas.

Embora você tenha agendado as execuções individuais no Google AdWords a cada hora, é possível definir a MIN_FREQUENCY de forma a espaçar os ciclos. Por exemplo, se você deseja gerar um relatório de planilha uma vez por semana para todas as suas contas, gere a planilha no gancho processFinalResults() e defina MIN_FREQUENCY como 7.

Como funciona

A parte fundamental do modelo está na seção chamada STANDARD TEMPLATE, que inclui a função main() do script. Um objeto stateManager expõe métodos para carregar e salvar o estado do ciclo das execuções anteriores.

No início de cada execução, main() carrega o estado anterior, inicia um novo ciclo se já tiver passado tempo suficiente desde o último ciclo concluído, determina um subconjunto de contas para processar, chama o gancho initializeCycle() se apropriado e chama o gancho initializeExecution(). Em seguida, ela chama executeByMode(), que processa o subconjunto de contas usando uma estratégia sequencial ou em paralelo. Ambas as estratégias chamam o gancho processAccount() para cada conta no subconjunto, eventualmente transmitindo os resultados para completeExecution(), que os salva no Drive. Por último, o modelo chama o gancho processIntermediateResults() e, se for o final do ciclo, também o gancho processFinalResults.

Embora a intenção do modelo seja o uso em uma conta de administrador, ele também pode ser executado em uma conta única. Pode ser útil executá-lo em uma conta única ao desenvolver e depurar sua lógica central. Você também pode pegar um script que usou anteriormente em uma conta de administrador e aplicá-lo a uma única conta sem qualquer modificação além de FILENAME.

Configuração

  • Configure um script com o código-fonte abaixo.
  • Siga as etapas em Configuração do script para concluir a configuração.

Código-fonte

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

Enviar comentários sobre…

Precisa de ajuda? Acesse nossa página de suporte.