Account Auditor - Single Account

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

多くの広告主様は、アカウントを一貫した構造にしたいと考えています。たとえば、各キャンペーンに配置する広告グループの数を固定にし、各広告グループに配置する広告やキーワードの数を固定にする広告主様もいらっしゃいます。他にも、ラベルを使ってアカウントを管理する広告主様もいらっしゃり、その場合はすべてのキャンペーンや広告グループに、いくつかの候補から選ばれたラベルが最低 1 つ設定されます。さらに、一度に 1 つの広告グループや広告だけが有効化されるよう管理する広告主様もいらっしゃいます。アカウントを一貫して同じ構造のまま維持するには、時間のかかる手作業が必要です。

アカウント検証ツールを使うと、キャンペーン、広告グループ、広告、キーワードの構造を検証できます。柔軟性のある「ルール」を使って、目標とする構造をスプレッドシートに設定すると、スクリプトがアカウントを分析して、スプレッドシードの個別のタブに記載したルールを満たさないエンティティを報告します。

アカウント検証ツールは、AdWords の自動化ルールと同様に、広告主様がアカウントに変更を加えたり、ルールに基づいてメールアラートを送信したりする場合に使用できる便利な機能です。大きく異なる点は、アカウント検証ツールがアカウントの構造条件(すべての有効なキャンペーンに、有効な広告グループが最低限の数以上に配置されているかなど)の確認に使用されるのに対し、自動化ルールが主に掲載結果の指標に重点を置いていることです。

設定

基本

アカウント検証ツールを設定するには、スプレッドシートでルールを指定します。こちらのスプレッドシートのテンプレートをコピーして、サンプルルールをご自身のルールで置き換えたり、変更や追加したりします。

上記の例では、次の事項についてご確認いただけます。

  • 各行は 1 つのルールを示しています。ルールは、一意の「ID」(必須)、「Trigger」(省略可)、「Test」(必須)の 3 つで構成されます。
  • 「Trigger」と「Test」には、「Entity Type」(必須)と「Conditions」(省略可)が含まれます。さらに「Test」には、「Conditions」を満たす必要があるエンティティの想定数(必須)が含まれます。
  • 「Trigger」や「Test」には複数の条件をリストでき、同じセル内(別の行ではない)でひとつずつ改行して入力します。
  • 「Test」では、すべてのエンティティが条件を満たすことを必須にしたり、条件を満たす必要があるエンティティの数を数値(例: 5 より大きい)で設定したりできます。

ルール

基本的に、それぞれの列は次のように解釈します: 「Trigger」の「Conditions」を満たすそれぞれの「Entity Type」に対し、対応する「Test」の「Entity Type」のエンティティの「Expected #」が、「Test」の「Conditions」を満たす必要がある。「Trigger」は、「Test」が適用されるエンティティを決定するためのフィルタだと考えることができます。たとえば、上記の例で定義されているルールは、次のとおりです。

  • ID 1、2、3: 有効なキャンペーンには、それぞれ最低 2 つの有効な広告グループが含まれている必要がある。有効な広告グループには、それぞれ最低 5 つの有効な広告が含まれている必要がある。また、有効な広告グループには、それぞれ有効なキーワードが最低 10 個含まれている必要がある。
  • ID 4: 有効なキャンペーンはすべて、予算がゼロであってはならない。
  • ID 5: 「Always Run」のラベルが設定されている有効な広告グループは、それぞれ有効なキャンペーンに含まれている必要がある。
  • ID 6: 広告見出しに「shoes」を含む有効なテキスト広告は、「shoes」を含む有効なキーワードに関連付けられている(つまり、同じ広告グループに含まれている)必要がある。
  • ID 7: 品質スコアが 3 を下回るキーワードはすべて無効である(つまり、有効なキーワードの数がゼロである)必要がある。
  • ID 8: すべての広告はテキスト広告である必要がある。

「Trigger」の設定は任意です。トリガーを設定しない場合、ID 8 のルールと同様に、「Test」の「Entity Type」に記載されているすべてのエンティティに「Test」が適用されます。

フォーマット

「Trigger」や「Test」の「Conditions」は、各エンティティ タイプの withCondition(condition) メソッドに従う必要があります。次の表のとおりです。

Entity Type フォーマット
Campaign CampaignSelector.withCondition(condition)
AdGroup AdGroupSelector.withCondition(condition)
Ad AdSelector.withCondition(condition)
Keyword KeywordSelector.withCondition(condition)

サポートされているのはエンティティ属性に関する条件だけです。掲載結果の表示項目に関する条件はサポートされていません。

複数の条件を指定するには、同じセル内(別の行ではない)でひとつずつ改行して入力します。複数の条件は AND で結合されます。

セル内で改行するには、セルのコンテンツの編集時に Ctrl+Enter キーか Option+Enter キーを押します。

「Test」の条件を満たすことが想定されるエンティティの数は、次の形式に従って入力します。

  • All は、「Test」のすべてのエンティティが「Conditions」を満たす必要があることを示します。
  • 数字は、「Test」の「Conditions」を満たさなければならないエンティティの数を示します。使用できるのは、0 以上の整数です。
  • 演算子と数字を組み合わせた形式(例: >= 3!= 0)を使用できます。使用できる演算子は =!=<><=>= です。= を使用した場合は、演算子を省略したのと同等になります。

「Trigger」の「Entity Type」が、「Test」の「Entity Type」と同じか、それよりも詳しい場合は、「Test」の「Expected #」は All にする必要があります。

スケジュール設定

アカウントの構造に大きな変更(キャンペーン、広告グループ、広告、キーワードの作成、除外、有効化、一時停止など)を加えるたびに、特定の方法で作成したエンティティでスクリプトが実行されるよう、スケジュールを設定します。アカウントの構造を変更する頻度が頻繁でない場合、スクリプト実行の頻度は「1 週間ごと」に設定すれば十分でしょう。ご自身やその他のスクリプトを通して、アカウントの構造を頻繁に変更する場合、スクリプト実行の頻度は「毎日」や「毎時間」に設定することをおすすめします。

仕組み

スクリプトは、スプレッドシートの各ルールを 1 つずつ読み込んで解析し、ID がない行で停止します。次に関連エンティティを取得して検査することで、1 つずつルールを確認していきます。「Trigger」の「Entity Type」は、「Test」の「Entity Type」より階層が上であったり下であったりすることがあります。また、兄弟グループである場合や、まったく同じである場合もあります。スクリプトは AdWords のエンティティ階層を認識して、各ルールを正しく適用します。

スクリプトでは、連想インデックス作成を利用して、AdWords のエンティティに必要なメソッドを柔軟に取得します。たとえば、スクリプト内の式 trigger[testInfo.selector]() の場合、trigger は AdWords スクリプトのエンティティであり、testInfo.selectorcampaignsadGroups などのセレクタを取得するメソッドの名前です。この式は trigger.campaigns()trigger.adGroups() に相当し、すべてのエンティティ タイプに対して AdWords スクリプト全体でメソッド名に一貫性があることを利用しています。

ルール違反が見つかった場合、その詳細がスプレッドシートの [Reports] タブに出力され、ユーザーにメールアラートが送信されます。[Reports] タブに出力されたデータには、ルールが適用された特定エンティティと、テストが失敗した理由が含まれます。テストが失敗した理由は、「Test」の「Conditions」を満たさなかった特定のエンティティ(「Conditions」を満たす必要のある「Test」のエンティティが All である場合)か、「Test」の「Conditions」を実際に満たすエンティティの数(「Expected #」が式である場合)のどちらかにあります。

設定方法

  • 下記のソースコードを使ってスクリプトを作成します。こちらのスプレッドシートのテンプレートのコピーを使用します。
  • 上記の設定で説明されているとおり、スプレッドシートにルールを追加します。
  • スクリプトの SPREADSHEET_URLRECIPIENT_EMAILS を必ず変更してください。
  • スクリプト実行のスケジュールを設定します。

ソースコード

// 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.');
  }
}

    

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

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