Add Shopping Product Listing Group Tree

Java
// Copyright 2018 Google LLC
//
// 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
//
//     https://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.

package com.google.ads.googleads.examples.shoppingads;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v1.common.ListingBrandInfo;
import com.google.ads.googleads.v1.common.ListingDimensionInfo;
import com.google.ads.googleads.v1.common.ListingGroupInfo;
import com.google.ads.googleads.v1.common.ProductConditionInfo;
import com.google.ads.googleads.v1.enums.AdGroupCriterionStatusEnum.AdGroupCriterionStatus;
import com.google.ads.googleads.v1.enums.ListingGroupTypeEnum.ListingGroupType;
import com.google.ads.googleads.v1.enums.ProductConditionEnum.ProductCondition;
import com.google.ads.googleads.v1.errors.GoogleAdsError;
import com.google.ads.googleads.v1.errors.GoogleAdsException;
import com.google.ads.googleads.v1.resources.AdGroupCriteriaName;
import com.google.ads.googleads.v1.resources.AdGroupCriterion;
import com.google.ads.googleads.v1.services.AdGroupCriterionOperation;
import com.google.ads.googleads.v1.services.AdGroupCriterionServiceClient;
import com.google.ads.googleads.v1.services.GoogleAdsRow;
import com.google.ads.googleads.v1.services.GoogleAdsServiceClient;
import com.google.ads.googleads.v1.services.GoogleAdsServiceClient.SearchPagedResponse;
import com.google.ads.googleads.v1.services.MutateAdGroupCriteriaResponse;
import com.google.ads.googleads.v1.services.MutateAdGroupCriterionResult;
import com.google.ads.googleads.v1.services.SearchGoogleAdsRequest;
import com.google.ads.googleads.v1.utils.ResourceNames;
import com.google.protobuf.Int64Value;
import com.google.protobuf.StringValue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Adds a shopping listing group tree to a shopping ad group. The example
 * will clear an existing listing group tree and rebuild it include the following tree structure:
 *
 * <pre>
 * ProductCanonicalCondition NEW $0.20
 * ProductCanonicalCondition USED $0.10
 * ProductCanonicalCondition null (everything else)
 *  ProductBrand CoolBrand $0.90
 *  ProductBrand CheapBrand $0.01
 *  ProductBrand null (everything else) $0.50
 * </pre>
 */
public class AddShoppingProductListingGroupTree {

  private static final int PAGE_SIZE = 1_000;

  private static class AddShoppingListingGroupParams extends CodeSampleParams {

    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private Long customerId;

    @Parameter(names = ArgumentNames.AD_GROUP_ID, required = true)
    private Long adGroupId;

    @Parameter(names = ArgumentNames.REPLACE_EXISTING_TREE, required = true, arity = 1)
    private Boolean replaceExistingTree;
  }

  public static void main(String[] args) {
    AddShoppingListingGroupParams params = new AddShoppingListingGroupParams();
    if (!params.parseArguments(args)) {
      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
      params.adGroupId = Long.parseLong("INSERT_AD_GROUP_ID_HERE");
      // Optional: To replace the existing listing group tree on an ad group set this parameter to
      // true.
      // This option will remove the existing listing group tree before creating a replacement.
      params.replaceExistingTree = Boolean.parseBoolean("INSERT_REPLACE_EXISTING_TREE_HERE");
    }

    GoogleAdsClient googleAdsClient;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      return;
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      return;
    }

    try {
      new AddShoppingProductListingGroupTree()
          .runExample(
              googleAdsClient, params.customerId, params.adGroupId, params.replaceExistingTree);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param adGroupId the ID of the ad group.
   * @param replaceExistingTree replace the existing listing group tree on the ad group, if it
   *     already exists. The example will throw a 'LISTING_GROUP_ALREADY_EXISTS' error if listing
   *     group tree already exists and this option is not set to true.
   * @throws GoogleAdsException if an API request failed with one or more service errors.
   */
  private void runExample(
      GoogleAdsClient googleAdsClient,
      long customerId,
      long adGroupId,
      boolean replaceExistingTree) {
    // 1) Optional: Removes the existing listing group tree, if it already exists on the ad group.
    if (replaceExistingTree) {
      removeListingGroupTree(googleAdsClient, customerId, adGroupId);
    }
    // Creates a list of ad group criterion to add.q
    List<AdGroupCriterionOperation> operations = new ArrayList<>();

    // 2) Constructs the listing group tree "root" node.

    // Subdivision node: (Root node)
    AdGroupCriterion adGroupCriterionRoot =
        createListingGroupSubdivisionRoot(customerId, adGroupId, -1L);
    // Get the resource name that will be used for the root node.
    // This resource has not been created yet and will include the temporary ID as part of the
    // criterion ID.
    String adGroupCriterionResourceNameRoot = adGroupCriterionRoot.getResourceName();
    operations.add(AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionRoot).build());

    // 3) Construct the listing group unit nodes for NEW, USED and other

    // Biddable Unit node: (Condition NEW node)
    // * Product Condition: NEW
    // * CPC bid: $0.20
    AdGroupCriterion adGroupCriterionConditionNew =
        createListingGroupUnitBiddable(
            customerId,
            adGroupId,
            adGroupCriterionResourceNameRoot,
            ListingDimensionInfo.newBuilder()
                .setProductCondition(
                    ProductConditionInfo.newBuilder().setCondition(ProductCondition.NEW).build())
                .build(),
            200_000L);
    operations.add(
        AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionConditionNew).build());

    // Biddable Unit node: (Condition USED node)
    // * Product Condition: USED
    // * CPC bid: $0.10
    AdGroupCriterion adGroupCriterionConditionUsed =
        createListingGroupUnitBiddable(
            customerId,
            adGroupId,
            adGroupCriterionResourceNameRoot,
            ListingDimensionInfo.newBuilder()
                .setProductCondition(
                    ProductConditionInfo.newBuilder().setCondition(ProductCondition.USED).build())
                .build(),
            100_000L);
    operations.add(
        AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionConditionUsed).build());

    // Sub-division node: (Condition "other" node)
    // * Product Condition: (not specified)
    AdGroupCriterion adGroupCriterionConditionOther =
        createListingGroupSubdivision(
            customerId,
            adGroupId,
            -2L,
            adGroupCriterionResourceNameRoot,
            ListingDimensionInfo.newBuilder()
                // All sibling nodes must have the same dimension type, even if they don't contain a
                // bid.
                // parent
                .setProductCondition(ProductConditionInfo.newBuilder().build())
                .build());
    // Gets the resource name that will be used for the condition other node.
    // This resource has not been created yet and will include the temporary ID as part of the
    // criterion ID.
    String adGroupCriterionResourceNameConditionOther =
        adGroupCriterionConditionOther.getResourceName();
    operations.add(
        AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionConditionOther).build());

    // 4) Constructs the listing group unit nodes for CoolBrand, CheapBrand and other

    // Biddable Unit node: (Brand CoolBrand node)
    // * Brand: CoolBrand
    // * CPC bid: $0.90
    AdGroupCriterion adGroupCriterionBrandCoolBrand =
        createListingGroupUnitBiddable(
            customerId,
            adGroupId,
            adGroupCriterionResourceNameConditionOther,
            ListingDimensionInfo.newBuilder()
                .setListingBrand(
                    ListingBrandInfo.newBuilder().setValue(StringValue.of("CoolBrand")).build())
                .build(),
            900_000L);
    operations.add(
        AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionBrandCoolBrand).build());

    // Biddable Unit node: (Brand CheapBrand node)
    // * Brand: CheapBrand
    // * CPC bid: $0.01
    AdGroupCriterion adGroupCriterionBrandCheapBrand =
        createListingGroupUnitBiddable(
            customerId,
            adGroupId,
            adGroupCriterionResourceNameConditionOther,
            ListingDimensionInfo.newBuilder()
                .setListingBrand(
                    ListingBrandInfo.newBuilder().setValue(StringValue.of("CheapBrand")).build())
                .build(),
            10_000L);
    operations.add(
        AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionBrandCheapBrand).build());

    // Biddable Unit node: (Brand other node)
    // * Brand: CheapBrand
    // * CPC bid: $0.01
    AdGroupCriterion adGroupCriterionBrandOther =
        createListingGroupUnitBiddable(
            customerId,
            adGroupId,
            adGroupCriterionResourceNameConditionOther,
            ListingDimensionInfo.newBuilder()
                .setListingBrand(ListingBrandInfo.newBuilder().build())
                .build(),
            50_000L);
    operations.add(
        AdGroupCriterionOperation.newBuilder().setCreate(adGroupCriterionBrandOther).build());

    // Issues a mutate request to add the ad group criterion to the ad group.
    try (AdGroupCriterionServiceClient adGroupCriterionServiceClient =
        googleAdsClient.getLatestVersion().createAdGroupCriterionServiceClient()) {
      List<MutateAdGroupCriterionResult> mutateAdGroupCriteriaResults =
          adGroupCriterionServiceClient
              .mutateAdGroupCriteria(Long.toString(customerId), operations)
              .getResultsList();
      for (MutateAdGroupCriterionResult mutateAdGroupCriterionResult :
          mutateAdGroupCriteriaResults) {
        System.out.printf(
            "Added ad group criterion for listing group with resource name: '%s'%n",
            mutateAdGroupCriterionResult.getResourceName());
      }
    }
  }

  /**
   * Removes all the ad group criteria that define the existing listing group tree for an ad group.
   * Returns without an error if all listing group criterion are successfully removed.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param adGroupId the ID of the ad group that the new listing group tree will be removed from.
   * @throws GoogleAdsException if an API request failed with one or more service errors.
   */
  private void removeListingGroupTree(
      GoogleAdsClient googleAdsClient, long customerId, long adGroupId) {
    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      String searchQuery =
          "SELECT ad_group_criterion.resource_name "
              + "FROM ad_group_criterion "
              + "WHERE ad_group_criterion.type = LISTING_GROUP "
              + "AND ad_group_criterion.listing_group.parent_ad_group_criterion IS NULL "
              + String.format("AND ad_group.id = %d", adGroupId);

      // Creates a request that will retrieve all listing groups where the parent ad group criterion
      // is NULL (and hence the root node in the tree) for a given ad group id.
      SearchGoogleAdsRequest request =
          SearchGoogleAdsRequest.newBuilder()
              .setCustomerId(Long.toString(customerId))
              .setPageSize(PAGE_SIZE)
              .setQuery(searchQuery)
              .build();

      // Issues the search request.
      SearchPagedResponse searchPagedResponse = googleAdsServiceClient.search(request);
      // Iterates over all rows in all pages to find the ad group criterion to remove.
      for (GoogleAdsRow googleAdsRow : searchPagedResponse.iterateAll()) {
        AdGroupCriterion adGroupCriterion = googleAdsRow.getAdGroupCriterion();
        System.out.printf(
            "Found ad group criterion with the resource name: '%s'.%n",
            adGroupCriterion.getResourceName());

        AdGroupCriterionOperation operation =
            AdGroupCriterionOperation.newBuilder()
                .setRemove(adGroupCriterion.getResourceName())
                .build();

        try (AdGroupCriterionServiceClient adGroupCriterionServiceClient =
            googleAdsClient.getLatestVersion().createAdGroupCriterionServiceClient()) {
          MutateAdGroupCriteriaResponse response =
              adGroupCriterionServiceClient.mutateAdGroupCriteria(
                  Long.toString(customerId), Collections.singletonList(operation));
          System.out.printf("Removed %d ad group criteria.%n", response.getResultsCount());
        }
      }
    }
  }

  /**
   * Creates a new criterion containing a biddable unit listing group node.
   *
   * @param customerId the client customer ID.
   * @param adGroupId the ID of the ad group.
   * @param parentAdGroupCriterionResourceName the resource name of the parent of this criterion.
   * @param listingDimensionInfo the ListingDimensionInfo to be set for this listing group.
   * @param cpcBidMicros the CPC bid for items in this listing group. This value should be specified
   *     in micros.
   * @return the ad group criterion object that contains the biddable unit listing group node.
   */
  private AdGroupCriterion createListingGroupUnitBiddable(
      long customerId,
      long adGroupId,
      String parentAdGroupCriterionResourceName,
      ListingDimensionInfo listingDimensionInfo,
      long cpcBidMicros) {

    String adGroupResourceName = ResourceNames.adGroup(customerId, adGroupId);
    // Note: There are two approaches for creating new unit nodes:
    // (1) Set the ad group resource name on the criterion (no temporary ID required).
    // (2) Use a temporary ID to construct the criterion resource name and set it using
    // setResourceName.
    // In both cases you must set the parentAdGroupCriterionResourceName on the listing
    // group for non-root nodes.
    // This example demonstrates method (1).
    AdGroupCriterion adGroupCriterion =
        AdGroupCriterion.newBuilder()
            // The ad group the listing group will be attached to.
            .setAdGroup(StringValue.of(adGroupResourceName))
            .setStatus(AdGroupCriterionStatus.ENABLED)
            .setListingGroup(
                ListingGroupInfo.newBuilder()
                    // Sets the type as a UNIT, which will allow the group to be biddable.
                    .setType(ListingGroupType.UNIT)
                    // Sets the ad group criterion resource name for the parent listing group.
                    // This can include a temporary ID if the parent criterion is not yet created.
                    // Use StringValue to convert from a String to a compatible argument type.
                    .setParentAdGroupCriterion(StringValue.of(parentAdGroupCriterionResourceName))
                    // Case values contain the listing dimension used for the node.
                    .setCaseValue(listingDimensionInfo)
                    .build())
            // Sets the bid for this listing group unit.
            // This will be used as the CPC bid for items that are included in this listing group
            .setCpcBidMicros(Int64Value.of(cpcBidMicros))
            .build();

    return adGroupCriterion;
  }

  /**
   * Creates a new criterion containing a subdivision listing group node.
   *
   * @param customerId the client customer ID.
   * @param adGroupId the ID of the ad group.
   * @param adGroupCriterionId the ID of the criterion. This value will used to construct the
   *     resource name. This can be a negative number if the criterion is yet to be created.
   * @param parentAdGroupCriterionResourceName the resource name of the parent of this criterion.
   * @param listingDimensionInfo the ListingDimensionInfo to be set for this listing group.
   * @return the ad group criterion object that contains the subdivision listing group node.
   */
  private AdGroupCriterion createListingGroupSubdivision(
      long customerId,
      long adGroupId,
      long adGroupCriterionId,
      String parentAdGroupCriterionResourceName,
      ListingDimensionInfo listingDimensionInfo) {

    String adGroupCriterionResourceName =
        ResourceNames.adGroupCriterion(customerId, adGroupId, adGroupCriterionId);
    AdGroupCriterion adGroupCriterion =
        AdGroupCriterion.newBuilder()
            // The resource name the criterion will be created with. This will define the ID for the
            // ad group criterion.
            .setResourceName(adGroupCriterionResourceName)
            .setStatus(AdGroupCriterionStatus.ENABLED)
            .setListingGroup(
                ListingGroupInfo.newBuilder()
                    // Sets the type as a SUBDIVISION, which will allow the node to be the parent of
                    // another sub-tree.
                    .setType(ListingGroupType.SUBDIVISION)
                    // Sets the ad group criterion resource name for the parent listing group.
                    // This can include a temporary ID if the parent criterion is not yet created.
                    // Uses StringValue to convert from a String to a compatible argument type.
                    .setParentAdGroupCriterion(StringValue.of(parentAdGroupCriterionResourceName))
                    // Case values contain the listing dimension used for the node.
                    .setCaseValue(listingDimensionInfo)
                    .build())
            .build();

    return adGroupCriterion;
  }

  /**
   * Creates a new criterion containing a root subdivision listing group node.
   *
   * @param customerId the client customer ID.
   * @param adGroupId the ID of the ad group.
   * @param adGroupCriterionId the ID of the criterion. This value will used to construct the
   *     resource name. This can be a negative number if the criterion is yet to be created.
   * @return the ad group criterion object that contains the listing group root node.
   */
  private AdGroupCriterion createListingGroupSubdivisionRoot(
      long customerId, long adGroupId, long adGroupCriterionId) {

    String adGroupCriterionResourceName =
        ResourceNames.adGroupCriterion(customerId, adGroupId, adGroupCriterionId);
    AdGroupCriterion adGroupCriterion =
        AdGroupCriterion.newBuilder()
            // The resource name the criterion will be created with. This will define the ID for the
            // ad group criterion.
            .setResourceName(adGroupCriterionResourceName)
            .setStatus(AdGroupCriterionStatus.ENABLED)
            .setListingGroup(
                ListingGroupInfo.newBuilder()
                    // Sets the type as a SUBDIVISION, which will allow the node to be the parent of
                    // another sub-tree.
                    .setType(ListingGroupType.SUBDIVISION)
                    .build())
            .build();

    return adGroupCriterion;
  }
}
C#
// Copyright 2018 Google LLC
//
// 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.

using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V1.Errors;
using Google.Ads.GoogleAds.V1.Common;
using Google.Ads.GoogleAds.V1.Resources;
using Google.Ads.GoogleAds.V1.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using static Google.Ads.GoogleAds.V1.Enums.AdGroupCriterionStatusEnum.Types;
using static Google.Ads.GoogleAds.V1.Enums.ListingGroupTypeEnum.Types;
using static Google.Ads.GoogleAds.V1.Enums.ProductConditionEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V1
{
    /// <summary>
    /// This code example shows how to add a shopping listing group tree to a shopping ad group.
    /// The example will clear an existing listing group tree and rebuild it include the following
    /// tree structure:
    ///
    /// <code>
    /// ProductCanonicalCondition NEW $0.20
    /// ProductCanonicalCondition USED $0.10
    /// ProductCanonicalCondition null (everything else)
    ///  ProductBrand CoolBrand $0.90
    ///  ProductBrand CheapBrand $0.01
    ///  ProductBrand null (everything else) $0.50
    /// </code>
    /// </summary>
    public class AddShoppingProductListingGroupTree : ExampleBase
    {
        /// <summary>
        /// The page size to be used by default.
        /// </summary>
        private const int PAGE_SIZE = 1_000;

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            AddShoppingProductListingGroupTree codeExample =
                new AddShoppingProductListingGroupTree();
            Console.WriteLine(codeExample.Description);

            // The Google Ads customer ID for which the call is made.
            long customerId = long.Parse("INSERT_CUSTOMER_ID_HERE");

            // The ID of the ad group.
            long adGroupId = long.Parse("INSERT_AD_GROUP_ID_HERE");

            // The boolean to indicate whether to replace the existing listing group tree on the
            // ad group, if it already exists. The example will throw a
            // 'LISTING_GROUP_ALREADY_EXISTS' error if listing group tree already exists and this
            // option is not set to true.
            bool replaceExistingTree = bool.Parse("INSERT_REPLACE_EXISTING_TREE_HERE");

            codeExample.Run(new GoogleAdsClient(), customerId, adGroupId, replaceExistingTree);
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example shows how to add a shopping listing group tree to a " +
                    "shopping ad group. The example will clear an existing listing group tree " +
                    "and rebuild it include the following tree structure: \n" +
                    "ProductCanonicalCondition NEW $0.20\n" +
                    "ProductCanonicalCondition USED $0.10\n" +
                    "ProductCanonicalCondition null (everything else)\n" +
                    "  ProductBrand CoolBrand $0.90\n" +
                    "  ProductBrand CheapBrand $0.01\n" +
                    "  ProductBrand null (everything else) $0.50\n";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The Google Ads customer ID for which the call is made.</param>
        /// <param name="adGroupId">The ID of the ad group.</param>
        /// <param name="replaceExistingTree">The boolean to indicate whether to replace the
        /// existing listing group tree on the ad group, if it already exists. The example will
        /// throw a <code>LISTING_GROUP_ALREADY_EXISTS</code> error if listing group tree already
        /// exists and this option is not set to true.</param>
        public void Run(GoogleAdsClient client, long customerId, long adGroupId,
            bool replaceExistingTree)
        {
            // Get the AdGroupCriterionService.
            AdGroupCriterionServiceClient adGroupCriterionService =
                client.GetService(Services.V1.AdGroupCriterionService);

            try
            {
                // 1) Optional: Remove the existing listing group tree, if it already exists on the
                // ad group.
                if (replaceExistingTree)
                {
                    RemoveListingGroupTree(client, customerId, adGroupId);
                }
                // Create a list of ad group criterion to add
                List<AdGroupCriterionOperation> operations = new List<AdGroupCriterionOperation>();

                // 2) Construct the listing group tree "root" node.

                // Subdivision node: (Root node)
                AdGroupCriterion adGroupCriterionRoot = CreateListingGroupSubdivisionRoot(
                    customerId, adGroupId, -1L);

                // Get the resource name that will be used for the root node.
                // This resource has not been created yet and will include the temporary ID as
                // part of the criterion ID.
                String adGroupCriterionResourceNameRoot = adGroupCriterionRoot.ResourceName;
                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionRoot
                });

                // 3) Construct the listing group unit nodes for NEW, USED and other

                // Biddable Unit node: (Condition NEW node)
                // * Product Condition: NEW
                // * CPC bid: $0.20
                AdGroupCriterion adGroupCriterionConditionNew =
                    CreateListingGroupUnitBiddable(
                        customerId,
                        adGroupId,
                        adGroupCriterionResourceNameRoot,
                        new ListingDimensionInfo()
                        {
                            ProductCondition = new ProductConditionInfo()
                            {
                                Condition = ProductCondition.New
                            }
                        },
                        200_000L);
                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionConditionNew
                });

                // Biddable Unit node: (Condition USED node)
                // * Product Condition: USED
                // * CPC bid: $0.10
                AdGroupCriterion adGroupCriterionConditionUsed =
                    CreateListingGroupUnitBiddable(
                        customerId,
                        adGroupId,
                        adGroupCriterionResourceNameRoot,
                        new ListingDimensionInfo()
                        {
                            ProductCondition = new ProductConditionInfo()
                            {
                                Condition = ProductCondition.Used
                            }
                        },
                        100_000L
                    );
                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionConditionUsed
                });

                // Sub-division node: (Condition "other" node)
                // * Product Condition: (not specified)
                AdGroupCriterion adGroupCriterionConditionOther =
                    CreateListingGroupSubdivision(
                        customerId,
                        adGroupId,
                        -2L,
                        adGroupCriterionResourceNameRoot,
                        new ListingDimensionInfo()
                        {
                            // All sibling nodes must have the same dimension type, even if they
                            // don't contain a bid.
                            ProductCondition = new ProductConditionInfo()
                        }
                    );
                // Get the resource name that will be used for the condition other node.
                // This resource has not been created yet and will include the temporary ID as
                // part of the criterion ID.
                String adGroupCriterionResourceNameConditionOther =
                    adGroupCriterionConditionOther.ResourceName;
                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionConditionOther
                });

                // 4) Construct the listing group unit nodes for CoolBrand, CheapBrand and other

                // Biddable Unit node: (Brand CoolBrand node)
                // * Brand: CoolBrand
                // * CPC bid: $0.90
                AdGroupCriterion adGroupCriterionBrandCoolBrand =
                    CreateListingGroupUnitBiddable(
                        customerId,
                        adGroupId,
                        adGroupCriterionResourceNameConditionOther,
                        new ListingDimensionInfo()
                        {
                            ListingBrand = new ListingBrandInfo()
                            {
                                Value = "CoolBrand"
                            }
                        },
                        900_000L);
                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionBrandCoolBrand
                });

                // Biddable Unit node: (Brand CheapBrand node)
                // * Brand: CheapBrand
                // * CPC bid: $0.01
                AdGroupCriterion adGroupCriterionBrandCheapBrand =
                    CreateListingGroupUnitBiddable(
                        customerId,
                        adGroupId,
                        adGroupCriterionResourceNameConditionOther,
                        new ListingDimensionInfo()
                        {
                            ListingBrand = new ListingBrandInfo()
                            {
                                Value = "CheapBrand"
                            }
                        },
                        10_000L);

                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionBrandCheapBrand
                });

                // Biddable Unit node: (Brand other node)
                // * Brand: CheapBrand
                // * CPC bid: $0.01
                AdGroupCriterion adGroupCriterionBrandOther =
                    CreateListingGroupUnitBiddable(
                        customerId,
                        adGroupId,
                        adGroupCriterionResourceNameConditionOther,
                        new ListingDimensionInfo()
                        {
                            ListingBrand = new ListingBrandInfo()
                        },
                        50_000L);
                operations.Add(new AdGroupCriterionOperation()
                {
                    Create = adGroupCriterionBrandOther
                });

                // Issues a mutate request to add the ad group criterion to the ad group.
                MutateAdGroupCriteriaResponse response =
                    adGroupCriterionService.MutateAdGroupCriteria(
                        customerId.ToString(), operations);

                // Display the results.
                foreach (MutateAdGroupCriterionResult mutateAdGroupCriterionResult
                    in response.Results)
                {
                    Console.WriteLine("Added ad group criterion for listing group with resource " +
                        $"name: '{mutateAdGroupCriterionResult.ResourceName}.");
                }
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
            }
        }

        /// <summary>
        /// Removes all the ad group criteria that define the existing listing group tree for an
        /// ad group. Returns without an error if all listing group criterion are successfully
        /// removed.
        /// </summary>
        /// <param name="client">The Google Ads API client..</param>
        /// <param name="customerId">The client customer ID.</param>
        /// <param name="adGroupId">The ID of the ad group that the new listing group tree will
        /// be removed from.</param>
        /// <exception cref="GoogleAdsException">Thrown if an API request failed with one or more
        /// service errors.</exception>
        private void RemoveListingGroupTree(GoogleAdsClient client, long customerId,
            long adGroupId)
        {
            // Get the GoogleAdsService.
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V1.GoogleAdsService);

            // Get the AdGroupCriterionService.
            AdGroupCriterionServiceClient adGroupCriterionService =
                client.GetService(Services.V1.AdGroupCriterionService);

            String searchQuery = "SELECT ad_group_criterion.resource_name FROM " +
                "ad_group_criterion WHERE ad_group_criterion.type = LISTING_GROUP AND " +
                "ad_group_criterion.listing_group.parent_ad_group_criterion IS NULL " +
                $"AND ad_group.id = {adGroupId}";

            // Creates a request that will retrieve all listing groups where the parent ad group
            // criterion is NULL (and hence the root node in the tree) for a given ad group ID.
            SearchGoogleAdsRequest request = new SearchGoogleAdsRequest()
            {
                CustomerId = customerId.ToString(),
                PageSize = PAGE_SIZE,
                Query = searchQuery
            };

            // Issues the search request.
            GoogleAdsRow googleAdsRow = googleAdsService.Search(request).First();

            AdGroupCriterion adGroupCriterion = googleAdsRow.AdGroupCriterion;
            Console.WriteLine("Found ad group criterion with the resource name: '{0}'.",
                adGroupCriterion.ResourceName);

            AdGroupCriterionOperation operation = new AdGroupCriterionOperation()
            {
                Remove = adGroupCriterion.ResourceName
            };

            MutateAdGroupCriteriaResponse response =
                adGroupCriterionService.MutateAdGroupCriteria(
                    customerId.ToString(), new AdGroupCriterionOperation[] { operation });
            Console.WriteLine($"Removed {response.Results.Count}.");
        }

        /// <summary>
        /// Creates a new criterion containing a biddable unit listing group node.
        /// </summary>
        /// <param name="customerId">The client customer ID.</param>
        /// <param name="adGroupId">The ID of the ad group.</param>
        /// <param name="parentAdGroupCriterionResourceName">The resource name of the parent of
        /// this criterion.</param>
        /// <param name="listingDimensionInfo">The ListingDimensionInfo to be set for this listing
        /// group.</param>
        /// <param name="cpcBidMicros">The CPC bid for items in this listing group. This value
        /// should be specified in micros.</param>
        /// <returns>The ad group criterion object that contains the biddable unit listing group
        /// node.</returns>
        private AdGroupCriterion CreateListingGroupUnitBiddable(long customerId, long adGroupId,
            String parentAdGroupCriterionResourceName, ListingDimensionInfo listingDimensionInfo,
            long cpcBidMicros)
        {
            String adGroupResourceName = ResourceNames.AdGroup(customerId, adGroupId);
            AdGroupCriterion adGroupCriterion = new AdGroupCriterion()
            {
                // The resource name the ad group the listing group node will be attached to unit.
                // Note: Listing group units do not require temporary IDs if ad group resource name
                // and parentAdGroupCriterionResourceName are specified. To use temporary IDs for
                // unit criteria, use ResourceName property.
                AdGroup = adGroupResourceName,
                Status = AdGroupCriterionStatus.Enabled,
                ListingGroup = new ListingGroupInfo()
                {
                    // Set the type as a UNIT, which will allow the group to be biddable
                    Type = ListingGroupType.Unit,

                    // Set the ad group criterion resource name for the parent listing group.
                    // This can include a criterion ID if the parent criterion is not yet created.
                    // Use StringValue to convert from a String to a compatible argument type.
                    ParentAdGroupCriterion = parentAdGroupCriterionResourceName,

                    // Case values contain the listing dimension used for the node.
                    CaseValue = listingDimensionInfo
                },

                // Set the bid for this listing group unit.
                // This will be used as the CPC bid for items that are included in this
                // listing group
                CpcBidMicros = cpcBidMicros
            };
            return adGroupCriterion;
        }

        /// <summary>
        /// Creates a new criterion containing a subdivision listing group node.
        /// </summary>
        /// <param name="customerId">The client customer ID.</param>
        /// <param name="adGroupId">The ID of the ad group.</param>
        /// <param name="adGroupCriterionId">The ID of the criterion. This value will used to
        /// construct the resource name. This can be a negative number if the criterion is yet to
        /// be created.</param>
        /// <param name="parentAdGroupCriterionResourceName">The resource name of the parent of
        /// this criterion.</param>
        /// <param name="listingDimensionInfo">The ListingDimensionInfo to be set for this listing
        /// group.</param>
        /// <returns>The ad group criterion object that contains the subdivision listing group
        /// node.</returns>
        private AdGroupCriterion CreateListingGroupSubdivision(long customerId, long adGroupId,
            long adGroupCriterionId, String parentAdGroupCriterionResourceName,
            ListingDimensionInfo listingDimensionInfo)
        {
            String adGroupCriterionResourceName = ResourceNames.AdGroupCriterion(
                customerId, adGroupId, adGroupCriterionId);

            AdGroupCriterion adGroupCriterion = new AdGroupCriterion()
            {
                // The resource name the criterion will be created with. This will define the
                // ID for the ad group criterion.
                ResourceName = adGroupCriterionResourceName,
                Status = AdGroupCriterionStatus.Enabled,
                ListingGroup = new ListingGroupInfo()
                {
                    Type = ListingGroupType.Subdivision,

                    // Set the ad group criterion resource name for the parent listing group.
                    // This can include a criterion ID if the parent criterion is not yet created.
                    // Use StringValue to convert from a String to a compatible argument type.
                    ParentAdGroupCriterion = parentAdGroupCriterionResourceName,

                    // Case values contain the listing dimension used for the node.
                    CaseValue = listingDimensionInfo
                }
            };

            return adGroupCriterion;
        }

        /// <summary>
        /// Creates a new criterion containing a root subdivision listing group node.
        /// </summary>
        /// <param name="customerId">The client customer ID.</param>
        /// <param name="adGroupId">The ID of the ad group.</param>
        /// <param name="adGroupCriterionId">The ID of the criterion. This value will used to
        /// construct the resource name. This can be a negative number if the criterion is yet
        /// to be created.</param>
        /// <returns>The ad group criterion object that contains the listing group root node.
        /// </returns>
        private AdGroupCriterion CreateListingGroupSubdivisionRoot(long customerId, long adGroupId,
            long adGroupCriterionId)
        {
            String adGroupCriterionResourceName = ResourceNames.AdGroupCriterion(customerId,
                adGroupId, adGroupCriterionId);

            AdGroupCriterion adGroupCriterion = new AdGroupCriterion()
            {
                // The resource name the criterion will be created with. This will define the ID
                // for the ad group criterion.
                ResourceName = adGroupCriterionResourceName,
                Status = AdGroupCriterionStatus.Enabled,
                ListingGroup = new ListingGroupInfo()
                {
                    // Set the type as a SUBDIVISION, which will allow the node to be the parent of
                    // another sub-tree.
                    Type = ListingGroupType.Subdivision
                }
            };
            return adGroupCriterion;
        }
    }
}
PHP
<?php
/**
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.
 */

namespace Google\Ads\GoogleAds\Examples\ShoppingAds;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Lib\V1\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V1\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V1\GoogleAdsException;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Util\V1\ResourceNames;
use Google\Ads\GoogleAds\V1\Common\ListingBrandInfo;
use Google\Ads\GoogleAds\V1\Common\ListingDimensionInfo;
use Google\Ads\GoogleAds\V1\Common\ListingGroupInfo;
use Google\Ads\GoogleAds\V1\Common\ProductConditionInfo;
use Google\Ads\GoogleAds\V1\Enums\AdGroupCriterionStatusEnum\AdGroupCriterionStatus;
use Google\Ads\GoogleAds\V1\Enums\ListingGroupTypeEnum\ListingGroupType;
use Google\Ads\GoogleAds\V1\Enums\ProductConditionEnum\ProductCondition;
use Google\Ads\GoogleAds\V1\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V1\Resources\AdGroupCriterion;
use Google\Ads\GoogleAds\V1\Services\AdGroupCriterionOperation;
use Google\Ads\GoogleAds\V1\Services\GoogleAdsRow;
use Google\ApiCore\ApiException;
use Google\Protobuf\Int64Value;
use Google\Protobuf\StringValue;

/**
 * This example shows how to add a shopping listing group tree to a shopping ad group. The example
 * will optionally clear an existing listing group tree and rebuild it to include the following tree
 * structure:
 *
 * <pre>
 * ProductCanonicalCondition NEW $0.20
 * ProductCanonicalCondition USED $0.10
 * ProductCanonicalCondition null (everything else)
 *  ProductBrand CoolBrand $0.90
 *  ProductBrand CheapBrand $0.01
 *  ProductBrand null (everything else) $0.50
 * </pre>
 */
class AddShoppingProductListingGroupTree
{
    const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    const AD_GROUP_ID = 'INSERT_AD_GROUP_ID_HERE';
    const SHOULD_REPLACE_EXISTING_TREE = 'INSERT_BOOLEAN_TRUE_OR_FALSE_HERE';

    const PAGE_SIZE = 1000;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::AD_GROUP_ID=> GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::SHOULD_REPLACE_EXISTING_TREE => GetOpt::REQUIRED_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::AD_GROUP_ID]
                    ?: self::AD_GROUP_ID,
                $options[ArgumentNames::SHOULD_REPLACE_EXISTING_TREE]
                    ?: self::SHOULD_REPLACE_EXISTING_TREE
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the client customer ID without hyphens
     * @param int $adGroupId the ad group ID
     * @param bool $shouldReplaceExistingTree true if it should replace the existing listing group
     *     tree on the ad group, if it already exists. The example will throw a
     *     'LISTING_GROUP_ALREADY_EXISTS' error if listing group tree already exists and this option
     *     is not set to true
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        $customerId,
        $adGroupId,
        $shouldReplaceExistingTree
    ) {
        // 1) Optional: Remove the existing listing group tree, if it already exists on the ad
        // group.
        if ($shouldReplaceExistingTree === "true") {
            self::removeListingGroupTree($googleAdsClient, $customerId, $adGroupId);
        }
        // Create a list of ad group criteria to add.
        $operations = [];

        // 2) Construct the listing group tree "root" node.

        // Subdivision node: (Root node)
        $adGroupCriterionRoot = self::createListingGroupSubdivision($customerId, $adGroupId);
        // Get the resource name that will be used for the root node.
        // This resource has not been created yet and will include the temporary ID as part of the
        // criterion ID.
        $adGroupCriterionResourceNameRoot = $adGroupCriterionRoot->getResourceName();
        $operations[] = new AdGroupCriterionOperation(['create' => $adGroupCriterionRoot]);

        // 3) Construct the listing group unit nodes for NEW, USED and other.

        // Biddable Unit node: (Condition NEW node)
        // * Product Condition: NEW
        // * CPC bid: $0.20
        $adGroupCriterionConditionNew = self::createListingGroupUnitBiddable(
            $customerId,
            $adGroupId,
            $adGroupCriterionResourceNameRoot,
            new ListingDimensionInfo([
                'product_condition' => new ProductConditionInfo(
                    ['condition' => ProductCondition::PBNEW]
                )
            ]),
            200000
        );
        $operations[] = new AdGroupCriterionOperation(['create' => $adGroupCriterionConditionNew]);

        // Biddable Unit node: (Condition USED node)
        // * Product Condition: USED
        // * CPC bid: $0.10
        $adGroupCriterionConditionUsed = self::createListingGroupUnitBiddable(
            $customerId,
            $adGroupId,
            $adGroupCriterionResourceNameRoot,
            new ListingDimensionInfo([
                'product_condition' => new ProductConditionInfo(
                    ['condition' => ProductCondition::USED]
                )
            ]),
            100000
        );
        $operations[] = new AdGroupCriterionOperation(['create' => $adGroupCriterionConditionUsed]);

        // Sub-division node: (Condition "other" node)
        // * Product Condition: (not specified)
        $adGroupCriterionConditionOther = self::createListingGroupSubdivision(
            $customerId,
            $adGroupId,
            $adGroupCriterionResourceNameRoot,
            new ListingDimensionInfo([
                // All sibling nodes must have the same dimension type, even if they don't contain a
                // bid.
                'product_condition' => new ProductConditionInfo()
            ])
        );
        $operations[] =
            new AdGroupCriterionOperation(['create' => $adGroupCriterionConditionOther]);

        // Get the resource name that will be used for the condition other node.
        // This resource has not been created yet and will include the temporary ID as part of the
        // criterion ID.
        $adGroupCriterionResourceNameConditionOther =
            $adGroupCriterionConditionOther->getResourceName();

        // 4) Construct the listing group unit nodes for CoolBrand, CheapBrand and other.

        // Biddable Unit node: (Brand CoolBrand node)
        // * Brand: CoolBrand
        // * CPC bid: $0.90
        $adGroupCriterionBrandCoolBrand = self::createListingGroupUnitBiddable(
            $customerId,
            $adGroupId,
            $adGroupCriterionResourceNameConditionOther,
            new ListingDimensionInfo([
                'listing_brand' => new ListingBrandInfo(
                    ['value' => new StringValue(['value' => 'CoolBrand'])]
                )
            ]),
            900000
        );
        $operations[] =
            new AdGroupCriterionOperation(['create' => $adGroupCriterionBrandCoolBrand]);

        // Biddable Unit node: (Brand CheapBrand node)
        // * Brand: CheapBrand
        // * CPC bid: $0.01
        $adGroupCriterionBrandCheapBrand = self::createListingGroupUnitBiddable(
            $customerId,
            $adGroupId,
            $adGroupCriterionResourceNameConditionOther,
            new ListingDimensionInfo([
                'listing_brand' => new ListingBrandInfo(
                    ['value' => new StringValue(['value' => 'CheapBrand'])]
                )
            ]),
            10000
        );
        $operations[] =
            new AdGroupCriterionOperation(['create' => $adGroupCriterionBrandCheapBrand]);

        // Biddable Unit node: (Brand other node)
        // * CPC bid: $0.05
        $adGroupCriterionBrandOtherBrand = self::createListingGroupUnitBiddable(
            $customerId,
            $adGroupId,
            $adGroupCriterionResourceNameConditionOther,
            new ListingDimensionInfo([
                'listing_brand' => new ListingBrandInfo()
            ]),
            50000
        );
        $operations[] =
            new AdGroupCriterionOperation(['create' => $adGroupCriterionBrandOtherBrand]);

        // Issues a mutate request.
        $adGroupCriterionServiceClient = $googleAdsClient->getAdGroupCriterionServiceClient();
        $response = $adGroupCriterionServiceClient->mutateAdGroupCriteria(
            $customerId,
            $operations
        );
        printf(
            'Added %d ad group criteria for listing group tree with the following resource '
            . 'names:%s',
            $response->getResults()->count(),
            PHP_EOL
        );
        foreach ($response->getResults() as $addedAdGroupCriterion) {
            /** @var AdGroupCriterion $addedAdGroupCriterion */
            print $addedAdGroupCriterion->getResourceName() . PHP_EOL;
        }
    }

    /**
     * Removes all the ad group criteria that define the existing listing group tree for an ad
     * group.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the client customer ID
     * @param int $adGroupId the ID of ad group that the existing listing group tree will be
     *     removed from
     */
    private static function removeListingGroupTree(
        GoogleAdsClient $googleAdsClient,
        $customerId,
        $adGroupId
    ) {
        $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
        // Creates a query that retrieves a listing group tree.
        $query = 'SELECT ad_group_criterion.resource_name '
            . 'FROM ad_group_criterion '
            . 'WHERE ad_group_criterion.type = LISTING_GROUP '
            . 'AND ad_group_criterion.listing_group.parent_ad_group_criterion IS NULL '
            . 'AND ad_group.id = ' . $adGroupId;

        // Issues a search request by specifying page size.
        $response =
            $googleAdsServiceClient->search($customerId, $query, ['pageSize' => self::PAGE_SIZE]);

        $operations = [];
        // Iterates over all rows in all pages and prints the requested field values for
        // the listing group tree in each row.
        foreach ($response->iterateAllElements() as $googleAdsRow) {
            /** @var GoogleAdsRow $googleAdsRow */
            $adGroupCriterion = $googleAdsRow->getAdGroupCriterion();
            printf(
                "Found an ad group criterion with the resource name: '%s'.%s",
                $adGroupCriterion->getResourceName(),
                PHP_EOL
            );

            // Creates an ad group criterion operation.
            $adGroupCriterionOperation = new AdGroupCriterionOperation();
            $adGroupCriterionOperation->setRemove($adGroupCriterion->getResourceName());
            $operations[] = $adGroupCriterionOperation;
        }
        if (count($operations) > 0) {
            // Issues a mutate request.
            $adGroupCriterionServiceClient = $googleAdsClient->getAdGroupCriterionServiceClient();
            $response = $adGroupCriterionServiceClient->mutateAdGroupCriteria(
                $customerId,
                $operations
            );
            printf("Removed %d ad group criteria.%s", $response->getResults()->count(), PHP_EOL);
        }
    }

    /**
     * Creates a new criterion containing a subdivision listing group node. If the parent ad group
     * criterion resource name is not specified, this method creates a root node.
     *
     * @param int $customerId the client customer ID
     * @param int $adGroupId the ad group ID
     * @param string|null $parentAdGroupCriterionResourceName the resource name of the parent of
     *     this criterion. If null, this method will create a root of the tree
     * @param ListingDimensionInfo|null $listingDimensionInfo the listing dimension info to be set
     *     for this listing group. This is required for non-root subdivisions
     * @return AdGroupCriterion the ad group criterion that contains the listing group root node
     */
    private static function createListingGroupSubdivision(
        $customerId,
        $adGroupId,
        $parentAdGroupCriterionResourceName = null,
        ListingDimensionInfo $listingDimensionInfo = null
    ) {
        static $tempId = 0;
        $listingGroupInfo = new ListingGroupInfo([
            // Set the type as a SUBDIVISION, which will allow the node to be the parent of
            // another sub-tree.
            'type' => ListingGroupType::SUBDIVISION
        ]);
        // If $parentAdGroupCriterionResourceName and $listingDimensionInfo are not null, create
        // a non-root division by setting its parent and case value.
        if (!is_null($parentAdGroupCriterionResourceName) && !is_null($listingDimensionInfo)) {
            // Set the ad group criterion resource name for the parent listing group.
            // This can include a temporary ID if the parent criterion is not yet created.
            // Use StringValue to convert from a string to a compatible argument type.
            $listingGroupInfo->setParentAdGroupCriterion(
                new StringValue(['value' => $parentAdGroupCriterionResourceName])
            );
            // Case values contain the listing dimension used for the node.
            $listingGroupInfo->setCaseValue($listingDimensionInfo);
        }

        $adGroupCriterion = new AdGroupCriterion([
            // The resource name the criterion will be created with. This will define the ID for the
            // ad group criterion.
            'resource_name' => ResourceNames::forAdGroupCriterion(
                $customerId,
                $adGroupId,
                // Specify a decreasing negative number as a temporary ad group criterion ID. The
                // ad group criterion will get the real ID when created on the server.
                --$tempId
            ),
            'status' => AdGroupCriterionStatus::ENABLED,
            'listing_group' => $listingGroupInfo
        ]);

        return $adGroupCriterion;
    }

    /**
     * Creates a new criterion containing a biddable unit listing group node.
     *
     * @param int $customerId the client customer ID
     * @param int $adGroupId the ad group ID
     * @param string $parentAdGroupCriterionResourceName the resource name of the parent of this
     *     criterion
     * @param ListingDimensionInfo $listingDimensionInfo the listing dimension info to be set for
     *     this listing group
     * @param int $cpcBidMicros the CPC bid for items in this listing group. This value should be
     *     specified
     * @return AdGroupCriterion the ad group criterion that contains the biddable unit listing
     *     group node
     */
    private static function createListingGroupUnitBiddable(
        $customerId,
        $adGroupId,
        $parentAdGroupCriterionResourceName,
        ListingDimensionInfo $listingDimensionInfo,
        $cpcBidMicros
    ) {
        // Note: There are two approaches for creating new unit nodes:
        // (1) Set the ad group resource name on the criterion (no temporary ID required).
        // (2) Use a temporary ID to construct the criterion resource name and set it using
        // setResourceName.
        // In both cases you must set the parentAdGroupCriterionResourceName on the listing
        // group for non-root nodes.
        // This example demonstrates method (1).
        $adGroupCriterion = new AdGroupCriterion([
            // The ad group the listing group will be attached to.
            'ad_group' => new StringValue(
                ['value' => ResourceNames::forAdGroup($customerId, $adGroupId)]
            ),
            'status' => AdGroupCriterionStatus::ENABLED,
            'listing_group' => new ListingGroupInfo([
                // Set the type as a UNIT, which will allow the group to be biddable.
                'type' => ListingGroupType::UNIT,
                // Set the ad group criterion resource name for the parent listing group.
                // This can include a temporary ID if the parent criterion is not yet created.
                // Use StringValue to convert from a string to a compatible argument type.
                'parent_ad_group_criterion' => new StringValue(
                    ['value' => $parentAdGroupCriterionResourceName]
                ),
                // Case values contain the listing dimension used for the node.
                'case_value' => $listingDimensionInfo
            ]),
            // Set the bid for this listing group unit.
            // This will be used as the CPC bid for items that are included in this listing group.
            'cpc_bid_micros' => new Int64Value(['value' => $cpcBidMicros])
        ]);

        return $adGroupCriterion;
    }
}

AddShoppingProductListingGroupTree::main();
Ruby
#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2019 Google LLC
#
# 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
#
#     https://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.
#
# This example shows how to add a shopping listing group tree to a shopping ad
# group. The example will optionally clear an existing listing group tree and
# rebuild it to include the following tree structure:
#
# ProductCanonicalCondition NEW $0.20
# ProductCanonicalCondition USED $0.10
# ProductCanonicalCondition null (everything else)
#   ProductBrand CoolBrand $0.90
#   ProductBrand CheapBrand $0.01
#   ProductBrand null (everything else) $0.50

require 'optparse'
require 'google/ads/google_ads'
require 'date'

def add_shopping_product_listing_group_tree(
    customer_id, ad_group_id, should_replace_existing_tree)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  # 1) Optional: Remove the existing listing group tree, if it already exists
  # on the ad group.
  if should_replace_existing_tree
    remove_listing_group_tree(client, customer_id, ad_group_id)
  end

  # 2) Construct the listing group tree "root" node.

  # Subdivision node: (Root node)
  ad_group_criterion_root = create_listing_group_subdivision(
    client,
    customer_id,
    ad_group_id,
  )
  # This resource has not been created yet and will include the temporary ID as
  # part of the criterion ID.
  ad_group_criterion_root_resource_name = ad_group_criterion_root.resource_name
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_root
  operations = [operation]

  # 3) Construct the listing group unit nodes for NEW, USED, and other.

  # Biddable Unit node: (Condition NEW node)
  # * Product Condition: NEW
  # * CPC bid: $0.20
  listing_dimension_info = client.resource(:ListingDimensionInfo)
  product_condition_info = client.resource(:ProductConditionInfo)
  product_condition_info.condition = :NEW
  listing_dimension_info.product_condition = product_condition_info
  ad_group_criterion_condition_new = create_listing_group_unit_biddable(
    client,
    customer_id,
    ad_group_id,
    ad_group_criterion_root_resource_name,
    listing_dimension_info,
    200_000,
  )
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_condition_new
  operations << operation

  # Biddable Unit node: (Condition USED node)
  # * Product Condition: USED
  # * CPC bid: $0.10
  listing_dimension_info = client.resource(:ListingDimensionInfo)
  product_condition_info = client.resource(:ProductConditionInfo)
  product_condition_info.condition = :USED
  listing_dimension_info.product_condition = product_condition_info
  ad_group_criterion_condition_used = create_listing_group_unit_biddable(
    client,
    customer_id,
    ad_group_id,
    ad_group_criterion_root_resource_name,
    listing_dimension_info,
    100_000,
  )
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_condition_used
  operations << operation

  # Sub-division node: (Condition "other" node)
  # * Product Condition: (not specified)
  listing_dimension_info = client.resource(:ListingDimensionInfo)
  listing_dimension_info.product_condition =
      client.resource(:ProductConditionInfo)
  ad_group_criterion_condition_other = create_listing_group_subdivision(
    client,
    customer_id,
    ad_group_id,
    ad_group_criterion_root_resource_name,
    listing_dimension_info,
  )
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_condition_other
  operations << operation

  ad_group_criterion_condition_other_resource_name =
      ad_group_criterion_condition_other.resource_name

  # 4) Construct the listing group unit nodes for CoolBrand, CheapBrand, and
  # other.

  # Biddable Unit node: (Brand CoolBrand node)
  # * Brand: CoolBrand
  # * CPC bid: $0.90
  listing_dimension_info = client.resource(:ListingDimensionInfo)
  listing_brand_info = client.resource(:ListingBrandInfo)
  listing_brand_info.value = client.wrapper.string("CoolBrand")
  listing_dimension_info.listing_brand = listing_brand_info
  ad_group_criterion_brand_cool_brand = create_listing_group_unit_biddable(
    client,
    customer_id,
    ad_group_id,
    ad_group_criterion_condition_other_resource_name,
    listing_dimension_info,
    900_000,
  )
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_brand_cool_brand
  operations << operation

  # Biddable Unit node: (Brand CheapBrand node)
  # * Brand: CheapBrand
  # * CPC bid: $0.01
  listing_dimension_info = client.resource(:ListingDimensionInfo)
  listing_brand_info = client.resource(:ListingBrandInfo)
  listing_brand_info.value = client.wrapper.string("CheapBrand")
  listing_dimension_info.listing_brand = listing_brand_info
  ad_group_criterion_brand_cheap_brand = create_listing_group_unit_biddable(
    client,
    customer_id,
    ad_group_id,
    ad_group_criterion_condition_other_resource_name,
    listing_dimension_info,
    10_000,
  )
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_brand_cheap_brand
  operations << operation

  # Biddable Unit node: (Brand other node)
  # * CPC bid: $0.05
  listing_dimension_info = client.resource(:ListingDimensionInfo)
  listing_dimension_info.listing_brand = client.resource(:ListingBrandInfo)
  ad_group_criterion_brand_other_brand = create_listing_group_unit_biddable(
    client,
    customer_id,
    ad_group_id,
    ad_group_criterion_condition_other_resource_name,
    listing_dimension_info,
    50_000,
  )
  operation = client.operation(:AdGroupCriterion)
  operation["create"] = ad_group_criterion_brand_other_brand
  operations << operation

  # Issue the mutate request.
  agc_service = client.service(:AdGroupCriterion)
  response = agc_service.mutate_ad_group_criteria(customer_id, operations)

  total_count = 0
  response.results.each do |added_criterion|
    puts "Added ad group criterion with name: #{added_criterion.resource_name}"
    total_count += 1
  end
  puts "#{total_count} criteria added in total."
end

def remove_listing_group_tree(client, customer_id, ad_group_id)
  ga_service = client.service(:GoogleAds)

  query = <<~QUERY
    SELECT
      ad_group_criterion.resource_name
    FROM
      ad_group_criterion
    WHERE
      ad_group_criterion.type = LISTING_GROUP
    AND
      ad_group_criterion.listing_group.parent_ad_group_criterion IS NULL
    AND
      ad_group.id = #{ad_group_id}
  QUERY

  response = ga_service.search(customer_id, query, page_size: PAGE_SIZE)

  operations = response.map do |row|
    criterion = row.ad_group_criterion
    puts "Found an ad group criterion with resource name: #{criterion.resource_name}"

    operation = client.operation(:AdGroupCriterion)
    operation["remove"] = criterion.resource_name
    operation
  end

  if operations.any?
    agc_service = client.service(:AdGroupCriterion)
    response = agc_service.mutate_ad_group_criteria(customer_id, operations)
    puts "Removed #{response.results.count} ad group criteria."
  end
end

# Specify a decreasing negative number for temporary ad group criteria IDs. The
# ad group criteria will get real IDs when created on the server.
# Returns -1, -2, -3, etc. on subsequent calls.
def next_id
  @id ||= 0
  @id -= 1
end

def create_listing_group_subdivision(client, customer_id, ad_group_id,
    parent_ad_group_criterion_name = nil, listing_dimension_info = nil)
  listing_group_info = client.resource(:ListingGroupInfo)
  listing_group_info.type = :SUBDIVISION

  if parent_ad_group_criterion_name && listing_dimension_info
    listing_group_info.parent_ad_group_criterion = client.wrapper.string(
      parent_ad_group_criterion_name
    )
    listing_group_info.case_value = listing_dimension_info
  end

  criterion = client.resource(:AdGroupCriterion)
  criterion.resource_name = client.path.ad_group_criterion(
    customer_id,
    ad_group_id,
    next_id,
  )
  criterion.status = :ENABLED
  criterion.listing_group = listing_group_info

  criterion
end

def create_listing_group_unit_biddable(client, customer_id, ad_group_id,
    parent_ad_group_criterion_name, listing_dimension_info, cpc_bid_micros)
  # Note: There are two approaches for creating new unit nodes:
  # (1) Set the ad group resource name on the criterion (no temporary ID
  # required).
  # (2) Use a temporary ID to construct the criterion resource name and set it
  # using the client.path utility.
  # In both cases you must set the parent ad group criterion's resource name on
  # the listing group for non-root nodes.
  # This example demonstrates method (1).
  criterion = client.resource(:AdGroupCriterion)
  criterion.ad_group = client.wrapper.string(client.path.ad_group(
    customer_id,
    ad_group_id,
  ))
  criterion.status = :ENABLED
  criterion.cpc_bid_micros = client.wrapper.int64(cpc_bid_micros)

  listing_group = client.resource(:ListingGroupInfo)
  # The type UNIT allows the group to be biddable.
  listing_group.type = :UNIT
  listing_group.parent_ad_group_criterion = client.wrapper.string(
    parent_ad_group_criterion_name
  )
  listing_group.case_value = listing_dimension_info
  criterion.listing_group = listing_group

  criterion
end

if __FILE__ == $0
  PAGE_SIZE = 1000

  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'
  options[:ad_group_id] = 'INSERT_AD_GROUP_ID_HERE'
  # Specifying any value for this field on the command line will override this
  # to true.
  options[:should_replace_existing_tree] = false

  OptionParser.new do |opts|
    opts.banner = sprintf("Usage: #{File.basename(__FILE__)} [options]")

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.on('-A', '--ad-group-id AD-GROUP-ID', String, 'Ad Group ID') do |v|
      options[:ad_group_id] = v
    end

    opts.on('-r', '--should-replace-existing-tree SHOULD-REPLACE-EXISTING-TREE',
        String, 'Create Default Listing Group') do |v|
      options[:should_replace_existing_tree] = true if v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    add_shopping_product_listing_group_tree(
      options.fetch(:customer_id).tr("-", ""),
      options.fetch(:ad_group_id),
      options.fetch(:should_replace_existing_tree),
    )
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      if error.location
        error.location.field_path_elements.each do |field_path_element|
          STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
        end
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED
        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
  rescue Google::Gax::RetryError => e
    STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \
        "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code,
                  e.cause.details, e.cause.metadata['request-id'])
  end
end

Send feedback about...

Google Ads API
Google Ads API
Need help? Visit our support page.