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.v5.common.ListingDimensionInfo;
import com.google.ads.googleads.v5.common.ListingGroupInfo;
import com.google.ads.googleads.v5.common.ProductBrandInfo;
import com.google.ads.googleads.v5.common.ProductConditionInfo;
import com.google.ads.googleads.v5.enums.AdGroupCriterionStatusEnum.AdGroupCriterionStatus;
import com.google.ads.googleads.v5.enums.ListingGroupTypeEnum.ListingGroupType;
import com.google.ads.googleads.v5.enums.ProductConditionEnum.ProductCondition;
import com.google.ads.googleads.v5.errors.GoogleAdsError;
import com.google.ads.googleads.v5.errors.GoogleAdsException;
import com.google.ads.googleads.v5.resources.AdGroupCriterion;
import com.google.ads.googleads.v5.services.AdGroupCriterionOperation;
import com.google.ads.googleads.v5.services.AdGroupCriterionServiceClient;
import com.google.ads.googleads.v5.services.GoogleAdsRow;
import com.google.ads.googleads.v5.services.GoogleAdsServiceClient;
import com.google.ads.googleads.v5.services.GoogleAdsServiceClient.SearchPagedResponse;
import com.google.ads.googleads.v5.services.MutateAdGroupCriteriaResponse;
import com.google.ads.googleads.v5.services.MutateAdGroupCriterionResult;
import com.google.ads.googleads.v5.services.SearchGoogleAdsRequest;
import com.google.ads.googleads.v5.utils.ResourceNames;
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 = null;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      System.exit(1);
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      System.exit(1);
    }

    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);
      }
      System.exit(1);
    }
  }

  /**
   * 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()
                .setProductBrand(ProductBrandInfo.newBuilder().setValue("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()
                .setProductBrand(ProductBrandInfo.newBuilder().setValue("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()
                .setProductBrand(ProductBrandInfo.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(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(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(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(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 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
//
//     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.V5.Errors;
using Google.Ads.GoogleAds.V5.Common;
using Google.Ads.GoogleAds.V5.Resources;
using Google.Ads.GoogleAds.V5.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using static Google.Ads.GoogleAds.V5.Enums.AdGroupCriterionStatusEnum.Types;
using static Google.Ads.GoogleAds.V5.Enums.ListingGroupTypeEnum.Types;
using static Google.Ads.GoogleAds.V5.Enums.ProductConditionEnum.Types;

namespace Google.Ads.GoogleAds.Examples.V5
{
    /// <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 =>
            "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.V5.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()
                        {
                            ProductBrand = new ProductBrandInfo()
                            {
                                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()
                        {
                            ProductBrand = new ProductBrandInfo()
                            {
                                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()
                        {
                            ProductBrand = new ProductBrandInfo()
                        },
                        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}");
                throw;
            }
        }

        /// <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.V5.GoogleAdsService);

            // Get the AdGroupCriterionService.
            AdGroupCriterionServiceClient adGroupCriterionService =
                client.GetService(Services.V5.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\V5\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V5\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V5\GoogleAdsException;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Util\V5\ResourceNames;
use Google\Ads\GoogleAds\V5\Common\ProductBrandInfo;
use Google\Ads\GoogleAds\V5\Common\ListingDimensionInfo;
use Google\Ads\GoogleAds\V5\Common\ListingGroupInfo;
use Google\Ads\GoogleAds\V5\Common\ProductConditionInfo;
use Google\Ads\GoogleAds\V5\Enums\AdGroupCriterionStatusEnum\AdGroupCriterionStatus;
use Google\Ads\GoogleAds\V5\Enums\ListingGroupTypeEnum\ListingGroupType;
use Google\Ads\GoogleAds\V5\Enums\ProductConditionEnum\ProductCondition;
use Google\Ads\GoogleAds\V5\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V5\Resources\AdGroupCriterion;
use Google\Ads\GoogleAds\V5\Services\AdGroupCriterionOperation;
use Google\Ads\GoogleAds\V5\Services\GoogleAdsRow;
use Google\ApiCore\ApiException;

/**
 * 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
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const AD_GROUP_ID = 'INSERT_AD_GROUP_ID_HERE';
    private const SHOULD_REPLACE_EXISTING_TREE = 'INSERT_BOOLEAN_TRUE_OR_FALSE_HERE';

    private 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
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @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,
        int $customerId,
        int $adGroupId,
        bool $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([
                'product_brand' => new ProductBrandInfo(['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([
                'product_brand' => new ProductBrandInfo(['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([
                'product_brand' => new ProductBrandInfo()
            ]),
            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 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,
        int $customerId,
        int $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 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(
        int $customerId,
        int $adGroupId,
        string $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.
            $listingGroupInfo->setParentAdGroupCriterion($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 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(
        int $customerId,
        int $adGroupId,
        string $parentAdGroupCriterionResourceName,
        ListingDimensionInfo $listingDimensionInfo,
        int $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' => 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.
                'parent_ad_group_criterion' => $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' => $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
  operations = [client.operation.create_resource.ad_group_criterion(ad_group_criterion_root)]

  # 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.listing_dimension_info do |ldi|
    ldi.product_condition = client.resource.product_condition_info do |pci|
      pci.condition = :NEW
    end
  end

  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.create_resource.ad_group_criterion(
    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.listing_dimension_info do |ldi|
    ldi.product_condition = client.resource.product_condition_info do |pci|
      pci.condition = :USED
    end
  end
  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.create_resource.ad_group_criterion(
    ad_group_criterion_condition_used
  )
  operations << operation

  # Sub-division node: (Condition "other" node)
  # * Product Condition: (not specified)
  listing_dimension_info = client.resource.listing_dimension_info do |ldi|
    ldi.product_condition = client.resource.product_condition_info
  end
  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.create_resource.ad_group_criterion(
    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.listing_dimension_info do |ldi|
    ldi.product_brand = client.resource.product_brand_info do |pbi|
      pbi.value = client.wrapper.string("CoolBrand")
    end
  end

  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.create_resource.ad_group_criterion(
    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.listing_dimension_info do |ldi|
    ldi.product_brand = client.resource.product_brand_info do |pbi|
      pbi.value = client.wrapper.string("CheapBrand")
    end
  end
  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.create_resource.ad_group_criterion(
    ad_group_criterion_brand_cheap_brand
  )
  operations << operation

  # Biddable Unit node: (Brand other node)
  # * CPC bid: $0.05
  listing_dimension_info = client.resource.listing_dimension_info do |ldi|
    ldi.product_brand = client.resource.product_brand_info
  end
  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.create_resource.ad_group_criterion(
    ad_group_criterion_brand_other_brand
  )
  operations << operation

  # Issue the mutate request.
  response = client.service.ad_group_criterion.mutate_ad_group_criteria(
    customer_id: customer_id,
    operations: 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.google_ads

  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: customer_id, query: 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}"

    client.operation.remove_resource.ad_group_criterion(criterion.resource_name)
  end

  if operations.any?
    response = client.service.ad_group_criterion.mutate_ad_group_criteria(
      customer_id: customer_id,
      operations: 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
)
  client.resource.ad_group_criterion do |criterion|
    criterion.resource_name = client.path.ad_group_criterion(
      customer_id,
      ad_group_id,
      next_id,
    )

    criterion.status = :ENABLED
    criterion.listing_group = client.resource.listing_group_info do |listing_group_info|
      listing_group_info.type = :SUBDIVISION

      if parent_ad_group_criterion_name && listing_dimension_info
        listing_group_info.parent_ad_group_criterion = parent_ad_group_criterion_name
        listing_group_info.case_value = listing_dimension_info
      end
    end
  end
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).
  client.resource.ad_group_criterion do |criterion|
    criterion.ad_group = client.path.ad_group(customer_id, ad_group_id)
    criterion.status = :ENABLED
    criterion.cpc_bid_micros = cpc_bid_micros

    criterion.listing_group = client.resource.listing_group_info do |listing_group|
      # The type UNIT allows the group to be biddable.
      listing_group.type = :UNIT
      listing_group.parent_ad_group_criterion = parent_ad_group_criterion_name
      listing_group.case_value = listing_dimension_info
    end
  end
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
    raise
  end
end

Perl

#!/usr/bin/perl -w
#
# 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
#
#     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.
#
# 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

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::Utils::SearchGoogleAdsIterator;
use Google::Ads::GoogleAds::V5::Resources::AdGroupCriterion;
use Google::Ads::GoogleAds::V5::Common::ListingGroupInfo;
use Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo;
use Google::Ads::GoogleAds::V5::Common::ProductConditionInfo;
use Google::Ads::GoogleAds::V5::Common::ProductBrandInfo;
use Google::Ads::GoogleAds::V5::Enums::ListingGroupTypeEnum
  qw(SUBDIVISION UNIT);
use Google::Ads::GoogleAds::V5::Enums::AdGroupCriterionStatusEnum qw(ENABLED);
use Google::Ads::GoogleAds::V5::Enums::ProductConditionEnum qw(NEW USED);
use
  Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation;
use
  Google::Ads::GoogleAds::V5::Services::GoogleAdsService::SearchGoogleAdsRequest;
use Google::Ads::GoogleAds::V5::Utils::ResourceNames;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd qw(abs_path);
use Data::Uniqid qw(uniqid);

use constant PAGE_SIZE => 1000;

# 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.
my $customer_id           = "INSERT_CUSTOMER_ID_HERE";
my $ad_group_id           = "INSERT_AD_GROUP_ID_HERE";
my $replace_existing_tree = undef;

sub add_shopping_product_listing_group_tree {
  my ($api_client, $customer_id, $ad_group_id, $replace_existing_tree) = @_;

  # 1) Optional: Remove the existing listing group tree, if it already exists
  # on the ad group.
  if ($replace_existing_tree) {
    remove_listing_group_tree($api_client, $customer_id, $ad_group_id);
  }

  # Create a list of ad group criteria operations to add.
  my $operations = [];

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

  # Subdivision node: (Root node)
  my $ad_group_criterion_root =
    create_listing_group_subdivision($customer_id, $ad_group_id);
  # 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.
  my $ad_group_criterion_root_resource_name =
    $ad_group_criterion_root->{resourceName};
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_root
    });

  # 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
  my $ad_group_criterion_condition_new = create_listing_group_unit_biddable(
    $customer_id,
    $ad_group_id,
    $ad_group_criterion_root_resource_name,
    Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo->new({
        productCondition =>
          Google::Ads::GoogleAds::V5::Common::ProductConditionInfo->new({
            condition => NEW
          })}
    ),
    200000
  );
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_condition_new
    });

  # Biddable Unit node: (Condition USED node)
  # * Product Condition: USED
  # * CPC bid: $0.10
  my $ad_group_criterion_condition_used = create_listing_group_unit_biddable(
    $customer_id,
    $ad_group_id,
    $ad_group_criterion_root_resource_name,
    Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo->new({
        productCondition =>
          Google::Ads::GoogleAds::V5::Common::ProductConditionInfo->new({
            condition => USED
          })}
    ),
    100000
  );
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_condition_used
    });

  # Sub-division node: (Condition "other" node)
  # * Product Condition: (not specified)
  my $ad_group_criterion_condition_other = create_listing_group_subdivision(
    $customer_id,
    $ad_group_id,
    $ad_group_criterion_root_resource_name,
    Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo->new({
        # All sibling nodes must have the same dimension type, even if they
        # don't contain a bid.
        productCondition =>
          Google::Ads::GoogleAds::V5::Common::ProductConditionInfo->new()}));
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_condition_other
    });

  # 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.
  my $ad_group_criterion_condition_other_resource_name =
    $ad_group_criterion_condition_other->{resourceName};

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

  # Biddable Unit node: (Brand CoolBrand node)
  # * Brand: CoolBrand
  # * CPC bid: $0.90
  my $ad_group_criterion_brand_cool_brand = create_listing_group_unit_biddable(
    $customer_id,
    $ad_group_id,
    $ad_group_criterion_condition_other_resource_name,
    Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo->new({
        productBrand =>
          Google::Ads::GoogleAds::V5::Common::ProductBrandInfo->new(
          {value => "CoolBrand"})}
    ),
    900000
  );
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_brand_cool_brand
    });

  # Biddable Unit node: (Brand CheapBrand node)
  # * Brand: CheapBrand
  # * CPC bid: $0.01
  my $ad_group_criterion_brand_cheap_brand = create_listing_group_unit_biddable(
    $customer_id,
    $ad_group_id,
    $ad_group_criterion_condition_other_resource_name,
    Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo->new({
        productBrand =>
          Google::Ads::GoogleAds::V5::Common::ProductBrandInfo->new(
          {value => "CheapBrand"})}
    ),
    10000
  );
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_brand_cheap_brand
    });

  # Biddable Unit node: (Brand other node)
  # * CPC bid: $0.05
  my $ad_group_criterion_brand_other_brand = create_listing_group_unit_biddable(
    $customer_id,
    $ad_group_id,
    $ad_group_criterion_condition_other_resource_name,
    Google::Ads::GoogleAds::V5::Common::ListingDimensionInfo->new({
        productBrand =>
          Google::Ads::GoogleAds::V5::Common::ProductBrandInfo->new()}
    ),
    50000
  );
  push @$operations,
    Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
    ->new({
      create => $ad_group_criterion_brand_other_brand
    });

  # Add the ad group criterion.
  my $ad_group_criterion_response =
    $api_client->AdGroupCriterionService()->mutate({
      customerId => $customer_id,
      operations => $operations
    });

  printf "Added %d ad group criteria for listing group tree with the " .
    "following resource names:\n",
    scalar @{$ad_group_criterion_response->{results}};

  foreach my $result (@{$ad_group_criterion_response->{results}}) {
    print $result->{resourceName}, "\n";
  }

  return 1;
}

# Removes all the ad group criteria that define the existing listing group
# tree for an ad group.
sub remove_listing_group_tree {
  my ($api_client, $customer_id, $ad_group_id) = @_;

  my $search_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";

  # Create a search Google Ads 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.
  my $search_request =
    Google::Ads::GoogleAds::V5::Services::GoogleAdsService::SearchGoogleAdsRequest
    ->new({
      customerId => $customer_id,
      query      => $search_query,
      pageSize   => PAGE_SIZE
    });

  # Get the GoogleAdsService.
  my $google_ads_service = $api_client->GoogleAdsService();

  my $iterator = Google::Ads::GoogleAds::Utils::SearchGoogleAdsIterator->new({
    service => $google_ads_service,
    request => $search_request
  });

  my $operations = [];
  # Iterate over all rows in all pages to find the ad group criterion to remove.
  while ($iterator->has_next) {
    my $google_ads_row     = $iterator->next;
    my $ad_group_criterion = $google_ads_row->{adGroupCriterion};
    printf "Found an ad group criterion with the resource name: '%s'.\n",
      $ad_group_criterion->{resourceName};

    # Create an ad group criterion operation.
    my $ad_group_criterion_operation =
      Google::Ads::GoogleAds::V5::Services::AdGroupCriterionService::AdGroupCriterionOperation
      ->new({
        remove => $ad_group_criterion->{resourceName}});

    push @$operations, $ad_group_criterion_operation;
  }

  if (scalar @$operations) {
    # Remove the ad group criterion that define the listing group tree.
    my $ad_group_criterion_response =
      $api_client->AdGroupCriterionService()->mutate({
        customerId => $customer_id,
        operations => $operations
      });

    printf "Removed %d ad group criteria.\n",
      scalar @{$ad_group_criterion_response->{results}};
  }
}

# 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.
sub create_listing_group_subdivision {
  my ($customer_id, $ad_group_id, $parent_ad_group_criterion_resource_name,
    $listing_dimension_info)
    = @_;

  my $listing_group_info =
    Google::Ads::GoogleAds::V5::Common::ListingGroupInfo->new({
      # Set the type as a SUBDIVISION, which will allow the node to be the
      # parent of another sub-tree.
      'type' => SUBDIVISION
    });

  # If $parent_ad_group_criterion_resource_name and $listing_dimension_info
  # are not null, create a non-root division by setting its parent and case value.
  if ($parent_ad_group_criterion_resource_name and $listing_dimension_info) {
    # 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.
    $listing_group_info->{parentAdGroupCriterion} =
      $parent_ad_group_criterion_resource_name;

    # Case values contain the listing dimension used for the node.
    $listing_group_info->{caseValue} = $listing_dimension_info;
  }

  my $ad_group_criterion =
    Google::Ads::GoogleAds::V5::Resources::AdGroupCriterion->new({
      # The resource name the criterion will be created with. This will define
      # the ID for the ad group criterion.
      resourceName =>
        Google::Ads::GoogleAds::V5::Utils::ResourceNames::ad_group_criterion(
        $customer_id, $ad_group_id, next_id()
        ),
      status       => ENABLED,
      listingGroup => $listing_group_info
    });

  return $ad_group_criterion;
}

# Creates a new criterion containing a biddable unit listing group node.
sub create_listing_group_unit_biddable {
  my ($customer_id, $ad_group_id, $parent_ad_group_criterion_resource_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
  # to the 'resourceName' attribute.
  # 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).
  my $ad_group_criterion =
    Google::Ads::GoogleAds::V5::Resources::AdGroupCriterion->new({
      adGroup => Google::Ads::GoogleAds::V5::Utils::ResourceNames::ad_group(
        $customer_id, $ad_group_id
      ),
      status       => ENABLED,
      listingGroup => Google::Ads::GoogleAds::V5::Common::ListingGroupInfo->new(
        {
          # Set the type as a UNIT, which will allow the group to be biddable.
          type => 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.
          parentAdGroupCriterion => $parent_ad_group_criterion_resource_name,
          # Case values contain the listing dimension used for the node.
          caseValue => $listing_dimension_info
        }
      ),
      # 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 => $cpc_bid_micros
    });

  return $ad_group_criterion;
}

# Specifies 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.
sub next_id {
  our $id ||= 0;
  $id -= 1;
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s"           => \$customer_id,
  "ad_group_id=i"           => \$ad_group_id,
  "replace_existing_tree=s" => \$replace_existing_tree
);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2) if not check_params($customer_id, $ad_group_id);

# Call the example.
add_shopping_product_listing_group_tree($api_client, $customer_id =~ s/-//gr,
  $ad_group_id, $replace_existing_tree);

=pod

=head1 NAME

add_shopping_product_listing_group_tree

=head1 DESCRIPTION

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

=head1 SYNOPSIS

add_shopping_product_listing_group_tree.pl [options]

    -help                           Show the help message.
    -customer_id                    The Google Ads customer ID.
    -ad_group_id                    The ad group ID.
    -replace_existing_tree          [optional] Replace the existing listing group tree
                                    on the ad group, if it already exists.

=cut