Auditor da conta: conta única

This script is for a single account. For operating on multiple accounts in a Manager Account, use the Manager Account version of the script.

Muitos anunciantes preferem organizar suas respectivas contas com uma estrutura consistente. Por exemplo, alguns deles estruturam cada campanha com um número fixo de grupos de anúncios e cada grupo de anúncios com um número fixo de anúncios e palavras-chave. Outros anunciantes usam rótulos para organizar as contas, de forma que cada campanha ou grupo de anúncios tenha pelo menos um rótulo de uma pequena lista. E ainda há quem organize os grupos de anúncios ou anúncios de modo que apenas um grupo de anúncios ou anúncio esteja ativo em um determinado momento. Garantir que sua conta continue consistente com a estrutura desejada exige trabalho manual e tempo.

O Auditor da conta ajuda a verificar a estrutura das suas campanhas, grupos de anúncios, anúncios e palavras-chave. Você define a estrutura desejada usando "regras" flexíveis em uma planilha, e o script analisa sua conta e informa sobre as entidades que não cumpriram suas regras em uma guia separada da planilha.

A funcionalidade do Auditor da conta é semelhante à das Regras automatizadas do Google AdWords, um recurso eficiente que permite que os anunciantes façam alterações nas contas ou enviem alertas por e-mail com base em regras. A principal diferença é que o Auditor da conta permite que você verifique as condições estruturais das suas contas (por exemplo, se cada campanha ativa tem pelo menos um número mínimo de grupos de anúncios ativos), ao passo que as Regras automatizadas se concentram principalmente em métricas de desempenho.

Configuração

Noções básicas

Para configurar o Auditor da conta, especifique regras em uma planilha. Você deve fazer uma cópia desta planilha modelo e substituir, modificar ou ampliar as regras de amostra usando seus próprios critérios.

O exemplo acima demonstra várias das características principais:

  • Cada linha representa uma única regra. Uma regra tem três partes: um ID único (obrigatório), um acionador (opcional) e um teste (obrigatório).
  • O acionador e o teste incluem um tipo de entidade (obrigatório) e condições (opcionais). O teste também indica o número esperado de entidades de teste que precisam cumprir as condições do teste (obrigatório).
  • O acionador e o teste podem ter várias condições se cada condição for colocada na sua própria linha dentro da mesma célula (e não em outra linha da planilha).
  • O teste pode especificar que todas as entidades devem cumprir as condições ou que uma condição numérica simples deve ser cumprida, por exemplo, haver mais de cinco entidades.

Regras

Em geral, uma linha pode ser interpretada da seguinte forma: "Para cada tipo de entidade de acionador que corresponde às condições de acionamento, o número esperado de entidades associadas do tipo de entidade de teste deve corresponder às condições do teste". Pense no acionador como um filtro que determina a quais entidades o teste se aplica. Por exemplo, as regras definidas no exemplo acima estão listadas abaixo.

  • IDs 1, 2, 3: toda campanha ativa precisa ter pelo menos dois grupos de anúncios ativos. Todo grupo de anúncios ativo precisa ter pelo menos cinco anúncios ativos. Por fim, todo grupo de anúncios ativo precisa ter pelo menos dez palavras-chave ativas.
  • ID 4: toda campanha ativa precisa ter um orçamento diferente de zero.
  • ID 5: todo grupo de anúncios ativo com o rótulo "Sempre veiculado" precisa estar em uma campanha ativa.
  • ID 6: todo anúncio de texto ativo cujo título contém "calçados" precisa estar associado (ou seja, ser parte do mesmo grupo de anúncios) a palavras-chave ativas que contenham "calçados".
  • ID 7: toda palavra-chave com Índice de qualidade inferior a três não pode estar ativa (ou seja, nenhuma dessas palavras-chave pode estar ativa).
  • ID 8: todos os anúncios precisam ser anúncios de texto.

O acionador é opcional. Quando omitido, como na regra ID 8, o teste é aplicado a todas as entidades do tipo da entidade do teste.

Formato

As condições do acionador ou do teste precisam seguir o formato do método do tipo de entidade withCondition(condition), como na tabela a seguir:

Tipo de entidade Formato das condições
Campanha CampaignSelector.withCondition(condition)
Grupo de anúncios AdGroupSelector.withCondition(condition)
Anúncio AdSelector.withCondition(condition)
Palavra-chave KeywordSelector.withCondition(condition)

Somente as condições que regem os atributos da entidade são compatíveis. As condições que regem as colunas de estatísticas não são compatíveis.

É possível especificar várias condições colocando uma delas em uma linha própria dentro da mesma célula (e não em uma linha separada). Várias condições são associadas usando o AND.

Para adicionar uma nova linha dentro de uma célula, pressione Ctrl + Enter ou Option + Enter ao editar o conteúdo da célula.

O número esperado de entidades de teste que cumprem as condições de teste precisa seguir um formato simples:

  • A palavra especial All significa que todas as entidades de teste precisam cumprir as condições de teste.
  • Um número significa exatamente que muitas entidades de teste precisam cumprir as condições de teste. O número precisa ser um número inteiro maior do que ou igual a zero.
  • É possível usar expressões simples do formulário Operator Number, como >= 3 ou != 0. Os operadores permitidos são =, !=, <, >, <= e >=. Usar o operador = é equivalente a omitir o operador.

Se o tipo de entidade do acionador for o mesmo ou mais específico que o tipo de entidade de teste, o número esperado especificado no teste precisará ser All.

Programação

Programe a execução do script com a mesma frequência com que você faz alterações estruturais significativas na sua conta, como criar, remover, ativar ou pausar campanhas, grupos de anúncios, anúncios e palavras-chave, nas entidades que você organiza de modo específico. Se você fizer alterações estruturais na sua conta com pouca frequência, a programação do script Semanal deverá ser suficiente. Se você faz mudanças estruturais na sua conta com frequência, seja manualmente ou por meio de outros scripts, convém programar o script Diariamente ou mesmo Por hora.

Como funciona

O script carregará e analisará cada regra uma por uma na planilha, parando na primeira linha que não tiver um ID. Em seguida, ele verifica uma regra de cada vez, recuperando e examinando as entidades relevantes. O tipo de entidade do acionador pode ser mais abrangente, mais restrito, irmão ou idêntico ao tipo de entidade de teste. O script compreende a hierarquia de entidades do Google AdWords e aplica cada regra de forma adequada.

O script usa a indexação associativa para receber os métodos necessários das entidades do Google AdWords de modo flexível. Por exemplo, a expressão no script trigger[testInfo.selector](), onde trigger é uma entidade do script do Google AdWords e testInfo.selector é o nome de um método para receber um selecionador como campaigns ou adGroups, é equivalente a trigger.campaigns() ou trigger.adGroups(), mais comuns. Essa é um forma de aproveitar o fato de que os métodos recebem nomes de modo consistente em todos os scripts do Google AdWords para todos os tipos de entidade.

Se o script encontrar alguma violação de regra, ele exibirá os detalhes na guia Relatórios da planilha e enviará um alerta por e-mail para uma lista de destinatários. As informações na guia Relatórios mostram a entidade específica que acionou a regra e o motivo pelo qual o teste falhou. O motivo é uma entidade específica que não cumpria as condições de teste (quando é esperado que All as entidades de teste cumpram as condições de teste) ou o número de entidades que realmente cumprem as condições de teste (quando o número esperado é uma expressão).

Configuração

  • Configure um script com o código-fonte abaixo. Use uma cópia desta planilha modelo.
  • Adicione regras à planilha conforme descrito acima em Configuração.
  • Não se esqueça de atualizar SPREADSHEET_URL e RECIPIENT_EMAILS no seu script.
  • Programe o script.

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 Account Auditor
 *
 * @overview The Account Auditor script helps you keep your accounts
 *     organized as you intend by automatically checking many types of
 *     structural conditions on your campaigns, ad groups, ads, and keywords.
 *     You define your intended structure using flexible "rules" in a
 *     spreadsheet, and the script analyzes your account and reports any
 *     entities that failed to satisfy your rules on a separate tab of the
 *     spreadsheet. See
 *     https://developers.google.com/adwords/scripts/docs/solutions/account-auditor
 *     for more details.
 *
 * @author AdWords Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 1.1.1
 *
 * @changelog
 * - version 1.1.1
 *   - Added validation for external spreadsheet setup.
 * - version 1.1
 *   - Added support for different ad types, including expanded text ads.
 * - version 1.0.1
 *   - Improvements to time zone handling. Minor bug fixes.
 * - version 1.0
 *   - Released initial version.
 */

var CONFIG = {
  // URL of the spreadsheet template.
  // This should be a copy of https://goo.gl/PW5s4b.
  SPREADSHEET_URL: 'YOUR_SPREADSHEET_URL',

  // Whether to output results to a copy of the above spreadsheet (true) or to
  // the spreadsheet directly, overwriting previous results (false).
  COPY_SPREADSHEET: false,

  // Array of addresses to be alerted via email if rule failures are found.
  RECIPIENT_EMAILS: [
    'YOUR_EMAIL_HERE'
  ]
};

// See applyRule() below for examples of each type.
var RULE_TYPES = {
  NO_TRIGGER: 'Rule applies to all entities of test entity type',
  BROAD_TRIGGER: 'Trigger entity type is broader than test entity type',
  NARROW_TRIGGER: 'Test entity type is broader than trigger entity type',
  SAME_TYPE: 'Trigger and test entity types are the same',
  SIBLING_TYPE: 'Trigger and test entity types are different but the same level'
};

// Static information for each type of entity.
// The keys in this table match the drop-down values in the spreadsheet and the
// values returned by getEntityType().
// selector: Name of the method used by a higher-level entity to retrieve a
//     selector for its descendant entities of this type.
// getter: Name of the method used by a lower-level entity to retrieve its
//     ancestor entity of this type.
// asText: Either:
//     (a) A string representing the method for this entity which returns a text
//         representation, OR
//     (b) A function, which when passed the entity as a single argument,
//         returns a text representation.
// level: Where the entity falls in the entity hierarchy. Lower numbers
//     represent broader entity types.
// parentType: The type of the entity's parent.
var ENTITY_TYPE_INFO = {
  Campaign: {
    selector: 'campaigns',
    getter: 'getCampaign',
    asText: 'getName',
    level: 1
  },
  AdGroup: {
    selector: 'adGroups',
    getter: 'getAdGroup',
    asText: 'getName',
    level: 2,
    parentType: 'Campaign'
  },
  Ad: {
    selector: 'ads',
    // For ads, asText references a specific function, not a method name. For
    // details refer to the above JSDoc or @see getEntityDetails definition.
    asText: getAdAsText,
    level: 3,
    parentType: 'AdGroup'
  },
  Keyword: {
    selector: 'keywords',
    asText: 'getText',
    level: 3,
    parentType: 'AdGroup'
  }
};

function main() {
  var failures = findFailures();

  var spreadsheet = validateAndGetSpreadsheet(CONFIG.SPREADSHEET_URL);
  if (CONFIG.COPY_SPREADSHEET) {
    spreadsheet = spreadsheet.copy('Account Auditor');
  }
  initializeSpreadsheet(spreadsheet);

  var hasFailures = outputFailures(spreadsheet,
    AdWordsApp.currentAccount().getCustomerId(), failures);

  if (hasFailures) {
    validateEmailAddresses();
    sendEmail(spreadsheet);
  }
}

/**
 * Checks the account against all of the rules in the spreadsheet.
 *
 * @return {Array.<Object>} A list of failures found.
 */
function findFailures() {
  var rules = loadRules();
  var failures = [];

  for (var i = 0; i < rules.length; i++) {
    failures = failures.concat(applyRule(rules[i]));
  }

  return failures;
}

/**
 * Saves failures to a spreadsheet if present.
 *
 * @param {Object} spreadsheet The spreadsheet object.
 * @param {string} customerId The account the failures are for.
 * @param {Array.<Object>} failures A list of failures.
 * @return {boolean} True if there were failures and false otherwise.
 */
function outputFailures(spreadsheet, customerId, failures) {
  if (failures.length > 0) {
    saveFailuresToSpreadsheet(spreadsheet, customerId, failures);
    Logger.log('Rule failures were found for ' + customerId +
               '. See ' + spreadsheet.getUrl());
    return true;
  } else {
    Logger.log('No rule failures were found for ' + customerId + '.');
    return false;
  }
}

/**
 * Sets up the spreadsheet to receive output.
 *
 * @param {Object} spreadsheet The spreadsheet object.
 */
function initializeSpreadsheet(spreadsheet) {
  // Make sure the spreadsheet is using the account's timezone.
  spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

  // Clear the last run date on the spreadsheet.
  spreadsheet.getRangeByName('RunDate').clearContent();

  // Clear all rows in the Report tab of the spreadsheet below the header row.
  var outputRange = spreadsheet.getRangeByName('ReportHeaders')
    .offset(1, 0, spreadsheet.getSheetByName('Report')
        .getDataRange().getLastRow())
    .clearContent();
}

/**
 * Saves failures for a particular account to the spreadsheet starting at the
 * first unused row.
 *
 * @param {Object} spreadsheet The spreadsheet object.
 * @param {string} customerId The account that the failures are for.
 * @param {Array.<Object>} failures A list of failure objects.
 */
function saveFailuresToSpreadsheet(spreadsheet, customerId, failures) {
  // Find the first open row on the Report tab below the headers and create a
  // range large enough to hold all of the failures, one per row.
  var lastRow = spreadsheet.getSheetByName('Report')
    .getDataRange().getLastRow();
  var headers = spreadsheet.getRangeByName('ReportHeaders');
  var outputRange = headers
    .offset(lastRow - headers.getRow() + 1, 0, failures.length);

  // Build each row of output values in the order of the Report tab columns.
  var outputValues = [];
  for (var i = 0; i < failures.length; i++) {
    var failure = failures[i];
    outputValues.push([
      customerId,
      failure.ruleId,
      '',                               // blank column
      failure.trigger.entityType,
      failure.trigger.entityId,
      failure.trigger.entityText,
      '',                               // blank column
      failure.test.entityType,
      failure.test.entityId || '',
      failure.test.entityText || '',

      // Include a leading apostrophe to ensure test expressions like "= 0" are
      // not mistakenly treated as formulas.
      failure.test.expected && ('\'' + failure.test.op + ' ' +
                                failure.test.expected) || '',
      failure.test.actual || ''
    ]);
  }
  outputRange.setValues(outputValues);

  spreadsheet.getRangeByName('RunDate').setValue(new Date());

  for (var i = 0; i < CONFIG.RECIPIENT_EMAILS.length; i++) {
    spreadsheet.addEditor(CONFIG.RECIPIENT_EMAILS[i]);
  }
}

/**
 * Sends an email alert that failures were found.
 *
 * @param {Object} spreadsheet The spreadsheet object.
 */
function sendEmail(spreadsheet) {
  MailApp.sendEmail(CONFIG.RECIPIENT_EMAILS.join(','),
      'Account Auditor Script: Rule Failures Were Found',
      'The Account Auditor Script found cases where the entities in your ' +
      'AdWords account(s) did not meet your rules. ' +
      'See the Report tab of ' + spreadsheet.getUrl() + ' for details.');
}

/**
 * Loads the rules from the spreadsheet.
 *
 * @return {Array.<Object>} A list of rule objects.
 */
function loadRules() {
  var rules = [];
  var spreadsheet = validateAndGetSpreadsheet(CONFIG.SPREADSHEET_URL);
  var lastRow = spreadsheet.getSheetByName('Rules').getDataRange().getLastRow();

  // Get all of the rows below the header row.
  var ruleData = spreadsheet.getRange('RuleHeaders')
      .offset(1, 0, lastRow).getValues();

  var ruleIds = {};
  var i = 0;

  while (ruleData[i][0]) {
    var rule = parseRule(ruleData[i]);
    rules.push(rule);

    // Rule IDs must be unique.
    if (!ruleIds[rule.id]) {
      ruleIds[rule.id] = true;
    } else {
      throw 'Multiple rules with ID ' + rule.id + '. Check the spreadsheet ' +
        'to make sure every rule has a unique ID.';
    }

    Logger.log('Loaded rule ' + rule.id + '.');
    i++;
  }

  Logger.log('Total number of rules: ' + i + '.');
  return rules;
}

/**
 * Parses a row from the spreadsheet into a rule object.
 *
 * @param {Array} ruleData A row from the spreadsheet.
 * @return {Object} A rule object.
 */
function parseRule(ruleData) {
  var rule = {
    id: ruleData[0].toString()
  };

  if (!rule.id) {
    throw 'Rule missing ID. Check the spreadsheet to make sure every rule ' +
        'has a unique ID.';
  }

  // Tests are required.
  if (!ruleData[5]) {
    throw 'Rule ' + rule.id + ' test is missing entity type.';
  } else {
    rule.test = {
      entityType: ruleData[5],
      conditions: ruleData[6] && ruleData[6].split('\n')
    };
  }

  // Triggers are optional, but it is invalid to have a condition without an
  // entity type.
  if (ruleData[3] && !ruleData[2]) {
    throw 'Rule ' + rule.id + ' trigger has conditions without an entity type.';
  } else if (ruleData[2]) {
    rule.trigger = {
      entityType: ruleData[2],
      conditions: ruleData[3] && ruleData[3].split('\n')
    };
  }

  // Count expressions are required.
  parsedExpr = parseCountExpr(ruleData[7]);
  if (!parsedExpr) {
    throw 'Rule ' + rule.id + ' has invalid expression for expected number.';
  }
  rule.test.op = parsedExpr.op;
  rule.test.num = parsedExpr.num;

  rule.type = getRuleType(rule);

  // Certain rule types can only use 'All'.
  if ((rule.type == RULE_TYPES.NARROW_TRIGGER ||
       rule.type == RULE_TYPES.SAME_TYPE) &&
      rule.test.op != 'All') {
    throw 'Rule ' + rule.id + ' must use "All" for the expected number.';
  }

  return rule;
}

/**
 * Parses a simple relational expression.
 *
 * @param {string} expr An expression of the form 'Op Num', where Op is one
 *     of '=', '!=', '<', '>', '<=', or '>=' and Num is a non-negative integer
 *     or the special expression 'All'. If Op is omitted it is assumed to be
 *     '='.
 * @return {Object} The parsed Op and Num, or undefined if the parse failed.
 */
function parseCountExpr(expr) {
  expr = expr.toString();

  // Check for the special expression 'All'.
  if (expr.match(/^\s*All\s*$/i)) {
    return {
      op: 'All'
    };
  }

  // If the operator is missing, prepend '=' as the default operator.
  if (expr.match(/^\s*\d*\s*$/)) {
    expr = '=' + expr;
  }

  var regex = /^\s*(\!?\=|\<\=?|\>\=?)\s*(\d*)\s*$/;
  var result = regex.exec(expr);

  if (result) {
    return {
      op: result[1],
      num: result[2]
    };
  }
}

/**
 * Determines the type of rule evaluation strategy to apply.
 *
 * @param {Object} rule A rule object.
 * @return {string} The type of rule evaluation strategy to apply.
 */
function getRuleType(rule) {
  if (!rule.trigger) {
    return RULE_TYPES.NO_TRIGGER;
  } else if (ENTITY_TYPE_INFO[rule.test.entityType].level >
      ENTITY_TYPE_INFO[rule.trigger.entityType].level) {
    return RULE_TYPES.BROAD_TRIGGER;
  } else if (ENTITY_TYPE_INFO[rule.test.entityType].level <
      ENTITY_TYPE_INFO[rule.trigger.entityType].level) {
    return RULE_TYPES.NARROW_TRIGGER;
  } else if (rule.test.entityType == rule.trigger.entityType) {
    return RULE_TYPES.SAME_TYPE;
  } else {
    return RULE_TYPES.SIBLING_TYPE;
  }
}

/**
 * Retrieves a text representation of an ad, casting the ad to the appropriate
 * type if necessary.
 *
 * @param {Ad} ad The ad object.
 * @return {string} The text representation.
 */
function getAdAsText(ad) {
  // There is no AdTypeSpace method for textAd.
  if (ad.getType() === 'TEXT_AD') {
    return ad.getHeadline();
  } else if (ad.isType().expandedTextAd()) {
    var eta = ad.asType().expandedTextAd();
    return eta.getHeadlinePart1() + ' - ' + eta.getHeadlinePart2();
  } else if (ad.isType().gmailImageAd()) {
    return ad.asType().gmailImageAd().getName();
  } else if (ad.isType().gmailMultiProductAd()) {
    return ad.asType().gmailMultiProductAd().getHeadline();
  } else if (ad.isType().gmailSinglePromotionAd()) {
    return ad.asType().gmailSinglePromotionAd().getHeadline();
  } else if (ad.isType().html5Ad()) {
    return ad.asType().html5Ad().getName();
  } else if (ad.isType().imageAd()) {
    return ad.asType().imageAd().getName();
  } else if (ad.isType().responsiveDisplayAd()) {
    return ad.asType().responsiveDisplayAd().getLongHeadline();
  }
  return 'N/A';
}

/**
 * Finds all cases where entities in the account fail to match a rule.
 *
 * @param {Object} rule A rule object.
 * @return {Array.<Object>} A list of failure objects, each describing a case
 *     where the rule was not met.
 */
function applyRule(rule) {
  Logger.log('Applying rule ' + rule.id + '.');

  var failures = [];

  var testInfo = ENTITY_TYPE_INFO[rule.test.entityType];

  // Get the trigger entities.
  if (rule.type != RULE_TYPES.NO_TRIGGER) {
    var triggerInfo = ENTITY_TYPE_INFO[rule.trigger.entityType];
    var triggerSelector = AdWordsApp[triggerInfo.selector]();
    addConditions(triggerSelector, rule.trigger.conditions);
    var triggers = triggerSelector.get();
  }

  // Helper method to get details about the entity associated with a failure.
  var getEntityDetails = function(entity) {
    var entityType = entity && entity.getEntityType();

    if (entityType) {
      var text = 'N/A';
      var fn = ENTITY_TYPE_INFO[entityType].asText;

      if (typeof fn === 'string') {
        // Specified as a string method name to get a text representation.
        text = entity[fn]();
      } else if (typeof fn === 'function') {
        // Specified as a function, to which the entity is passed to extract a
        // text representation. Used in the case of Ads, which can have a number
        // of underlying types.
        text = fn(entity);
      }
    }

    return {
      entityId: entity ? entity.getId() : 'N/A',
      entityType: entity ? entityType : 'N/A',
      entityText: text ? text : 'N/A'
    };
  };

  // Helper method to build failures for each entity in a selector that does
  // not match the test conditions.
  var checkAll = function(params) {
    var entities = findMissingEntities(params.selector, rule.test.conditions);

    for (var i = 0; i < entities.length; i++) {
      var entity = entities[i];

      // The trigger is either provided as a map, indicated to be the entity
      // itself, or provided directly (possibly null if there is no trigger).
      var trigger = params.triggerMap && params.triggerMap[entity.getId()] ||
        (params.trigger == 'entity' && entity || params.trigger);

      failures.push({
        ruleId: rule.id,
        trigger: getEntityDetails(trigger),
        test: getEntityDetails(entity)
      });
    }
  };

  // Helper method to build failures where the number of entities in a
  // selector does not match the test's expected number.
  var checkCount = function(params) {
    addConditions(params.selector, rule.test.conditions);
    var expected = rule.test.num;
    var actual = params.selector.get().totalNumEntities();
    var op = rule.test.op || '=';

    if (!compare(actual, expected, op)) {
      failures.push({
        ruleId: rule.id,
        trigger: getEntityDetails(params.trigger),
        test: {
          entityType: rule.test.entityType,
          expected: expected,
          op: op,
          actual: actual
        }
      });
    }
  };

  // Helper method to use the appropriate checker depending on the operator.
  var checkRule = function(params) {
    if (rule.test.op == 'All') {
      checkAll(params);
    } else {
      checkCount(params);
    }
  };

  switch (rule.type) {
    case RULE_TYPES.NO_TRIGGER:
      // Example: All campaigns are enabled (test).
      // Example: There are X enabled campaigns (test).
      checkRule({selector: AdWordsApp[testInfo.selector]()});
      break;

    case RULE_TYPES.NARROW_TRIGGER:
      // Example: For each enabled ad group (trigger), its campaign is enabled
      // (test).
      var testIds = [];
      var triggerMap = {};

      while (triggers.hasNext()) {
        var trigger = triggers.next();
        var testId = trigger[testInfo.getter]().getId();
        testIds.push(testId);
        triggerMap[testId] = trigger;
      }

      checkAll({
        triggerMap: triggerMap,
        selector: AdWordsApp[testInfo.selector]().withIds(testIds)
      });
      break;

    case RULE_TYPES.BROAD_TRIGGER:
      // Example: For each enabled campaign (trigger), all of its ad groups are
      // enabled (test).
      // Example: For each enabled campaign (trigger), it has at least X enabled
      // ad groups (test).
      while (triggers.hasNext()) {
        var trigger = triggers.next();
        var testSelector = trigger[testInfo.selector]();
        checkRule({trigger: trigger, selector: testSelector});
      }
      break;

    case RULE_TYPES.SAME_TYPE:
      // Example: For each ad group with label X (trigger), it is enabled
      // (test).
      checkAll({trigger: 'entity', selector: triggerSelector});
      break;

    case RULE_TYPES.SIBLING_TYPE:
      // Example: For each enabled ad (trigger), it is associated with at least
      // X approved keywords (test).
      var parentIds = {};

      while (triggers.hasNext()) {
        var trigger = triggers.next();
        var parent = trigger[ENTITY_TYPE_INFO[triggerInfo.parentType].getter]();
        var parentId = parent.getId();

        // If we have seen the parent already, it is unnecessary and inefficient
        // to check again since the siblings are the same. This means only the
        // first sibling will be listed as the trigger for any failures.
        if (!parentIds[parentId]) {
          parentIds[parentId] = true;
          var testSelector = parent[testInfo.selector]();
          checkRule({trigger: trigger, selector: testSelector});
        }
      }
      break;
  }

  return failures;
}

/**
 * Adds a set of conditions to a selector. The provided selector is modified.
 *
 * @param {Object} selector An AdWords scripts selector.
 * @param {Array.<string>} conditions An array of conditions to be applied to
 *     the selector using withCondition().
 */
function addConditions(selector, conditions) {
  if (conditions) {
    for (var i = 0; i < conditions.length; i++) {
      selector = selector.withCondition(conditions[i]);
    }
  }
}

/**
 * Retrieves the IDs of a set of AdWords scripts entities.
 *
 * @param {Object} iterator An iterator over an AdWords scripts entity that
 *     has a getId() method.
 * @return {Array.<number>} An array of IDs of the entities.
 */
function getEntityIds(iterator) {
  var ids = [];

  while (iterator.hasNext()) {
    ids.push(iterator.next().getId());
  }

  return ids;
}

/**
 * Identifies entities in a selector that do not match a set of conditions.
 * Modifies the given selector.
 *
 * @param {Object} selector An AdWords scripts selector for an entity type.
 *     that has a getId() method.
 * @param {Array.<string>} conditions An array of conditions to be applied to
 *     the selector using withCondition().
 * @return {Array.<Object>} A list of AdWords objects that did not meet the
 *     conditions.
 */
function findMissingEntities(selector, conditions) {
  var missing = [];

  // Get an iterator *before* applying the conditions.
  var entities = selector.get();

  // Get the IDs of the entities matching the conditions.
  addConditions(selector, conditions);
  var ids = getEntityIds(selector.get());

  while (entities.hasNext()) {
    var entity = entities.next();
    if (ids.indexOf(entity.getId()) == -1) {
      missing.push(entity);
    }
  }

  return missing;
}

/**
 * Compares two numbers using a given operator.
 *
 * @param {number} val1 The first number in the comparison.
 * @param {number} val2 The second number in the comparison.
 * @param {string} op The operator for the comparison.
 * @return {boolean} The result of the comparison 'val1 op val2'.
 */
function compare(val1, val2, op) {
  switch (op) {
    case '=':
      return val1 == val2;
    case '!=':
      return val1 != val2;
    case '<':
      return val1 < val2;
    case '>':
      return val1 > val2;
    case '<=':
      return val1 <= val2;
    case '>=':
      return val1 >= val2;
  }
}

/**
 * DO NOT EDIT ANYTHING BELOW THIS LINE.
 * Please modify your spreadsheet URL and email addresses at the top of the file
 * only.
 */

/**
 * Validates the provided spreadsheet URL and email address
 * to make sure that they're set up properly. Throws a descriptive error message
 * if validation fails.
 *
 * @param {string} spreadsheeturl The URL of the spreadsheet to open.
 * @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
 * @throws {Error} If the spreadsheet URL or email hasn't been set
 */
function validateAndGetSpreadsheet(spreadsheeturl) {
  if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
    throw new Error('Please specify a valid Spreadsheet URL. You can find' +
        ' a link to a template in the associated guide for this script.');
  }
  var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
  return spreadsheet;
}

/**
 * Validates the provided email address to make sure it's not the default.
 * Throws a descriptive error message if validation fails.
 *
 * @throws {Error} If the list of email addresses is still the default
 */
function validateEmailAddresses() {
  if (CONFIG.RECIPIENT_EMAILS &&
      CONFIG.RECIPIENT_EMAILS[0] == 'YOUR_EMAIL_HERE') {
    throw new Error('Please specify a valid email address.');
  }
}

    

Enviar comentários sobre…

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