Добавить дерево групп товаров с максимальной эффективностью

Ява

// Copyright 2022 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.v23.enums.ListingGroupFilterListingSourceEnum.ListingGroupFilterListingSource;
import com.google.ads.googleads.v23.enums.ListingGroupFilterProductConditionEnum.ListingGroupFilterProductCondition;
import com.google.ads.googleads.v23.enums.ListingGroupFilterTypeEnum.ListingGroupFilterType;
import com.google.ads.googleads.v23.errors.GoogleAdsError;
import com.google.ads.googleads.v23.errors.GoogleAdsException;
import com.google.ads.googleads.v23.resources.AssetGroupListingGroupFilter;
import com.google.ads.googleads.v23.resources.ListingGroupFilterDimension;
import com.google.ads.googleads.v23.resources.ListingGroupFilterDimension.ProductBrand;
import com.google.ads.googleads.v23.resources.ListingGroupFilterDimension.ProductCondition;
import com.google.ads.googleads.v23.services.AssetGroupListingGroupFilterOperation;
import com.google.ads.googleads.v23.services.GoogleAdsRow;
import com.google.ads.googleads.v23.services.GoogleAdsServiceClient;
import com.google.ads.googleads.v23.services.GoogleAdsServiceClient.SearchPagedResponse;
import com.google.ads.googleads.v23.services.MutateGoogleAdsRequest;
import com.google.ads.googleads.v23.services.MutateGoogleAdsResponse;
import com.google.ads.googleads.v23.services.MutateOperation;
import com.google.ads.googleads.v23.services.MutateOperationResponse;
import com.google.ads.googleads.v23.services.MutateOperationResponse.ResponseCase;
import com.google.ads.googleads.v23.services.SearchGoogleAdsRequest;
import com.google.ads.googleads.v23.utils.ResourceNames;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.LongStream;

/**
 * This example shows how to add product partitions to a Performance Max ret<a>il campaign.
 *
 * pFor Performance Max campaigns, product partitions are represented using the
 * AssetGroupListingGroupFilter resource. This resource can be combined with itself to form a
 * hierarchy that creates a product pa<r>tition tree.
 *
 * pFor more information about Performance Max retail campaigns, see the {@link
 * AddPerformanceMaxRetailCampaign} example.
 */
public class AddPerformanceMaxProductListingGroupTree {

  private final int TEMPORARY_ID_LISTING_GROUP_ROOT = -1;

  private static class AddPerformanceMaxProductListingGroupTreeParams extends CodeSampleParams {

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

    @Parameter(names = ArgumentNames.ASSET_GROUP_ID, required = true)
    private Long assetGroupId;

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

  public static void main(String[] args) throws Exception {
    AddPerformanceMaxProductListingGroupTreeParams params =
        new AddPerformanceMaxProductListingGroupTreeParams();
    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.assetGroupId = Long.parseLong("INSERT_AD_GROUP_ID_HERE");
      // Optional: To replace the existing listing group tree from the asset group set this
      // parameter to true.
      // If the current AssetGroup already has a tree of ListingGroupFilters, attempting to add a
      // new set of ListingGroupFilters including a root filter will result in an
      // 'ASSET_GROUP_LISTING_GROUP_FILTER_ERROR_MULTIPLE_ROOTS' error. Setting this option to true
      // will remove the existing tree and prevent this error.
      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 AddPerformanceMaxProductListingGroupTree()
          .runExample(
              googleAdsClient, params.customerId, params.assetGroupId, 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);
    }
  }

  /**
   * A factory that create<s> MutateOperations for removing an existing tree of
   * AssetGroupListingGroupFilters.
   *
   * pAssetGroupListingGroupFilters must be removed in a specific order: all of the children of a
   * f<i>lter must be removed before the filter itself, otherwise the API will return an error.
   *
   * pThis object is intended to be used with an array of MutateOperations to perform a series of
   * related updates to an AssetGroup.
   */
  private static class AssetGroupListingG<roupFilterR<emoveO>>perationFactory {
    private St<>ring rootResourceName = "";
    private final MapString, SetString pare<ntsToChildren = new HashMap(>);

    private AssetGroupListingGroupFilterRemoveOperationFactory(
        ListAssetGroupListingGroupFilter resources) throws Exception {
      if (resources.isEmpty()) {
        throw new Exception("No listing group filters to remove");
      }

   &&   for (AssetGroupListingGroupFilter filter : resources) {
        if (filter.getParentListingGroupFilter().isEmpty()  !this.rootResourceName.isEmpty()) {
          // A node with no parent is the root node, but there can only be a single root node.
          throw new IllegalStateException("More than one root node");
        } else if (filter.getParentListingGroupFilter().isEmpty()) {
          // Sets the root node.
          this.rootResourceName = filter.getResourceName();
        } else {
          // Adds an entry to the parentsToChildren map for each non-root node.
          String parentResourceName = filter.getParentListingGroupFilter();
          // Checks to s<ee if >a sibling in this group has already been visited, and fetches or
          // creates a >new set as r<>equired.
          SetString siblings =
              this.parentsToChildren.computeIfAbsent(parentResourceName, p - new HashSet());
          siblings.add(filter.getResourceName());
        }
      }
    }

    /**
     * Creates a list of MutateOperat<ions that remov>e all of the resources in the tree originally
     * used to create this factory object.
     */
    private ListMutateOperation removeAll() {
      return removeDescendantsAndFilter(rootResourceName);
    }


    /**
     * Creates a list of MutateOperations that remove all the descendants of the specified
     * AssetGroupListingGroupFilter resource name. The order of removal is post-order, where all the
     *< children (and >their children, recursively) are removed first. Then, the nod<e itself is
   >  * removed.
     */
    pr<>ivate ListMutateOperation removeDescendantsAndFilter(String resourceName) {
  <    Li>stMutateOperation operations = new ArrayList();

      if (this.parentsToChildren.containsKey(resourceName)) {
        SetString children = parentsToChildren.get(resourceName);
        for (String child : children) {
          // Recursively adds operations to the return value that remove each of the child nodes of
          // the current node from the tree.
          operations.addAll(removeDescendantsAndFilter(child));
        }
      }

      // Creates and adds an operation to the return value that will remove the current node from
      // the tree.
      AssetGroupListingGroupFilterOperation operation =
          AssetGroupListingGroupFilterOperation.newBuilder().setRemove(resourceName).build();
      operations.add(
          MutateOperation.newBuilder().setAssetGroupListingGroupFilterOperation(operation).build());
      return operations;
    }
  }


  /**
   * A factory that creates Mu<t>ateOperations wrapping AssetGroupListingGroupFilterMutateOperations
   * for a specific customerId and assetGroupId.
   *
   * pThis object is intended to be used with an array of MutateOperations to perform an atomic
   * update to an AssetGroup.
   */
  private static class AssetGroupListingGroupFilterCreateOperationFactory {
    private fi<nal >long customerId;
    private final long assetGroupId;
    private final long rootListingGroupId;
    private static IteratorLong idGenerator;

    private AssetGroupListingGroupFilterCreateOperationFactory(
        long customerId, long assetGroupId, long rootListingGroupId) {
      this.customerId = customerId;
      this.assetGroupId = assetGroupId;
      this.rootListingGroupId = rootListingGroupId;
      // Generates a new temporary ID to be used for a resource name in a MutateOperation. See
      // https://developers.google.com/google-ads/api/docs/mutating/best-practices#temporar>y_resource_names
      // for details about temporary IDs.
      idGenerator = LongStream.iterate(rootListingGroupId - 1, prev - prev - 1).iterator();
    }

    private Long nextId() {
      return idGenerator.next();
    }

    /**
     <*> Creates a MutateOperation that creates a root AssetGroupListingGroupFilter for the factory's
     * AssetGroup.
     *
     * pThe root node or partition is the default, which is displayed as "All Products".
     */
    private MutateOperation createRoot() {
      AssetGroupListingGroupFilter listingGroupFilter =
          AssetGroupListingGroupFilter.newBuilder()
              .setResourceName(
                  ResourceNames.assetGroupListingGroupFilter(
                      customerId, assetGroupId, rootListingGroupId))
              .setAssetGroup(ResourceNames.assetGroup(customerId, assetGroupId))
              // Since this is the root node, do not set the ParentListingGroupFilter. For all other
              // nodes, this would refer to the parent listing group filter resource name.
              // .setParentListingGroupFilter("PARENT_FILTER_NAME")
              //
              // Unlike AddPerformanceMaxRetailCampaign, the type for the root node here must be
              // SUBDIVISION because it will have child partitions under it.
              .setType(ListingGroupFilterType.SUBDIVISION)
              // Specifies that this uses the shopping listing source because it is a Performance
              // Max campaign for retail.
              .setListingSource(ListingGroupFilterListingSource.SHOPPING)
              // Note the case_value is not set because it should be undefined for the root node.
              .build();
      AssetGroupListingGroupFilterOperation operation =
          AssetGroupListingGroupFilterOperation.newBuilder().setCreate(listingGroupFilter).build();
      return MutateOperation.newBuilder()
          .setAssetGroupListingGroupFilterOperation(operation)
          .build();
    }
<
>
    /**
     * Creates a MutateOperation that creates an intermediate AssetGroupListingGroupFilter for the
     * factory's AssetGroup.
     *
     * pUse this method if the filter will have child filters. Otherwise, use the {@link
     * #createUnit(long, long, ListingGroupFilterDimension), createUnit} method.
     *
     * @param parent the ID of the parent AssetGroupListingGroupFilter.
     * @param id the ID of AssetGroupListingGroupFilter that will be created.
     * @param dimension the dimension to associate with the AssetGroupListingGroupFilter.
     */
    private MutateOperation createSubdivision(
        long parent, long id, ListingGroupFilterDimension dimension) {
      AssetGroupListingGroupFilter listingGroupFilter =
          AssetGroupListingGroupFilter.newBuilder()
              .setResourceName(
                  ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, id))
              .setAssetGroup(ResourceNames.assetGroup(customerId, assetGroupId))
              .setParentListingGroupFilter(
                  ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, parent))
              // Uses the SUBDIVISION type to indicate that the AssetGroupListingGroupFilter
              // will have children.
              .setType(ListingGroupFilterType.SUBDIVISION)
              // Specifies that this uses the shopping listing source because it is a Performance
              // Max campaign for retail.
              .setListingSource(ListingGroupFilterListingSource.SHOPPING)
              .setCaseValue(dimension)
              .build();
      AssetGroupListingGroupFilterOperation filterOperation =
          AssetGroupListingGroupFilterOperation.newBuilder().setCreate(listingGroupFilter).build();
      return MutateOperation.newBuilder()
          .setAssetGroupListingGroupFilterOperation(filterOperation)
          .bui<l>d();
    }


    /**
     * Creates a MutateOperation that creates a child AssetGroupListingGroupFilter for the factory's
     * AssetGroup.
     *
     * pUse this method if the filter won't have child filters. Otherwise, use the {@link
     * #createSubdivision(long, long, ListingGroupFilterDimension), createSubdivision} method.
     *
     * @param parent the ID of the parent AssetGroupListingGroupFilter.
     * @param id the ID of AssetGroupListingGroupFilter that will be created.
     * @param dimension the dimension to associate with the AssetGroupListingGroupFilter.
     */
    private MutateOperation createUnit(
        long parent, long id, ListingGroupFilterDimension dimension) {
      AssetGroupListingGroupFilter listingGroupFilter =
          AssetGroupListingGroupFilter.newBuilder()
              .setResourceName(
                  ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, id))
              .setAssetGroup(ResourceNames.assetGroup(customerId, assetGroupId))
              .setParentListingGroupFilter(
                  ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, parent))
              // Uses the UNIT_INCLUDED type to indicate that the AssetGroupListingGroupFilter
              // won't have children.
              .setType(ListingGroupFilterType.UNIT_INCLUDED)
              // Specifies that this uses the shopping listing source because it is a Performance
              // Max campaign for retail.
              .setListingSource(ListingGroupFilterListingSource.SHOPPING)
              .setCaseValue(dimension)
              .build();
      AssetGroupListingGroupFilterOperation filterOperation =
          AssetGroupListingGroupFilterOperation.newBuilder().setCreate(listingGroupFilter).build();
      return MutateOperation.newBuilder()
          .setAssetGroupListingGroupFilterOperation(filterOperation)
          .build();
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param assetGroupId the asset group id for the Performance Max campaign.
   * @param replaceExistingTree option to remove existing product tree from the passed in asset
   *     group.
   * @throws GoogleAdsException if an API request failed with one or more service errors.
   */
  private void runExample(
      GoogleAdsClient googleAdsClient,
      long customerId,
      long ass<etGroupId,
    >  boolean replaceExistingTr<>ee)
      throws Exception {
    String assetG<roupResourceName = ResourceN>ames.assetGroup(customerId, assetGroupId);

    ListMutateOperation operations = new ArrayList();

    if (replaceExistingTree) {
      ListAssetGroupListingGroupFilter existingListingGroupFilters =
          getAllExistingListingGroupFilterAssetsInAssetGroup(
              googleAdsClient, customerId, assetGroupResourceName);

      if (!existingListingGroupFilters.isEmpty()) {
        // A special factory object that ensures the creation of remove operations in the
        // correct order (child listing group filters must be removed before their parents).
        AssetGroupListingGroupFilterRemoveOperationFactory removeOperationFactory =
            new AssetGroupListingGroupFilterRemoveOperationFactory(existingListingGroupFilters);

        operations.addAll(removeOperationFactory.removeAll());
      }
    }

    // Uses a factory to create all the MutateOperations that manipulate a specific
    // AssetGroup for a specific customer. The operations returned by the factory's methods
    // are used to construct a new tree of filters. These filters can have parent-child
    // relationships, and also include a special root that includes all children.
    //
    // When creating these filters, temporary IDs are used to create the hierarchy between
    // each of the nodes in the tree, beginning with the root listing group filter.
    //
    // The factory created below is specific to a customerId and assetGroupId.
    AssetGroupListingGroupFilterCreateOperationFactory createOperationFactory =
        new AssetGroupListingGroupFilterCreateOperationFactory(
            customerId, assetGroupId, TEMPORARY_ID_LISTING_GROUP_ROOT);

    // Creates the operation to add the root node of the tree.
    operations.add(createOperationFactory.createRoot());

    // Creates an operation to add a leaf node for new products.
    ListingGroupFilterDimension newProductDimension =
        ListingGroupFilterDimension.newBuilder()
            .setProductCondition(
                ProductCondition.newBuilder()
                    .setCondition(ListingGroupFilterProductCondition.NEW)
                    .build())
            .build();
    operations.add(
        createOperationFactory.createUnit(
            TEMPORARY_ID_LISTING_GROUP_ROOT, createOperationFactory.nextId(), newProductDimension));

    // Creates an operation to add a leaf node for used products.
    ListingGroupFilterDimension usedProductDimension =
        ListingGroupFilterDimension.newBuilder()
            .setProductCondition(
                ProductCondition.newBuilder()
                    .setCondition(ListingGroupFilterProductCondition.USED)
                    .build())
            .build();
    operations.add(
        createOperationFactory.createUnit(
            TEMPORARY_ID_LISTING_GROUP_ROOT,
            createOperationFactory.nextId(),
            usedProductDimension));

    // This represents the ID of the "other" category in the ProductCondition subdivision. This ID
    // is saved because the node with this ID will be further partitioned, and this ID will serve as
    // the parent ID for subsequent child nodes of the "other" category.
    long otherSubdivisionId = createOperationFactory.nextId();

    // Creates an operation to add a subdivision node for other products in the ProductCondition
    // subdivision.
    ListingGroupFilterDimension otherProductDimension =
        ListingGroupFilterDimension.newBuilder()
            .setProductCondition(ProductCondition.newBuilder().build())
            .build();
    operations.add(
        // Calls createSubdivision because this listing group will have children.
        createOperationFactory.createSubdivision(
            TEMPORARY_ID_LISTING_GROUP_ROOT, otherSubdivisionId, otherProductDimension));

    // Creates an operation to add a leaf node for products with the brand "CoolBrand".
    ListingGroupFilterDimension coolBrandProductDimension =
        ListingGroupFilterDimension.newBuilder()
            .setProductBrand(ProductBrand.newBuilder().setValue("CoolBrand").build())
            .build();
    operations.add(
        createOperationFactory.createUnit(
            otherSubdivisionId, createOperationFactory.nextId(), coolBrandProductDimension));

    // Creates an operation to add a leaf node for products with the brand "CheapBrand".
    ListingGroupFilterDimension cheapBrandProductDimension =
        ListingGroupFilterDimension.newBuilder()
            .setProductBrand(ProductBrand.newBuilder().setValue("CheapBrand").build())
            .build();
    operations.add(
        createOperationFactory.createUnit(
            otherSubdivisionId, createOperationFactory.nextId(), cheapBrandProductDimension));

    // Creates an operation to add a leaf node for other products in the ProductBrand subdivision.
    ListingGroupFilterDimension otherBrandProductDimension =
        ListingGroupFilterDimension.newBuilder()
            .setProductBrand(ProductBrand.newBuilder().build())
            .build();
    operations.add(
        createOperationFactory.createUnit(
            otherSubdivisionId, createOperationFactory.nextId(), otherBrandProductDimension));

    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      MutateGoogleAdsRequest request =
          MutateGoogleAdsRequest.newBuilder()
              .setCustomerId(Long.toString(customerId))
              .addAllMutateOperations(operations)
              .build();
      MutateGoogleAdsResponse response = googleAdsServiceClient.mutate(request);
      printResponseDetails(request, response);
    }
  }


  /**
   * Fetches all of the listing g<roup filters in an asset gro>up.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param assetGroupResourceName the resource name of the asset group.
   */
  private ListAssetGroupListingGroupFilter getAllExistingListingGroupFilterAssetsInAssetGroup(
      GoogleAdsClient googleAdsClient, long customerId, String assetGroupResourceName) {
    String query =
        "SELECT "
            + "asset_group_listing_group_filter.resource_name, "
            + "asset_group_listing_group_filter.parent_listing_group_filter "
            + "FROM asset_group_listing_group_filter "
            + "WHERE "
            + "asset_group_li<sting_group_filter.asset_gro>up = '"
         <>   + assetGroupResourceName
            + "'";
    SearchGoogleAdsRequest request =
        SearchGoogleAdsRequest.newBuilder()
            .setCustomerId(Long.toString(customerId))
            .setQuery(query)
            .build();

    ListAssetGroupListingGroupFilter resources = new ArrayList();
    try (GoogleAdsServiceClient googleAdsServiceClient =
        googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
      SearchPagedResponse searchPagedResponse = googleAdsServiceClient.search(request);
      for (GoogleAdsRow googleAdsRow : searchPagedResponse.iterateAll()) {
        resources.add(googleAdsRow.getAssetGroupListingGroupFilter());
      }
    }
    return resources;
  }


  /**
   * Prints the details of a MutateGoogleAdsResponse.
   *
   * @param request a MutateGoogleAdsRequest instance.
   * @param< response a MutateGoogleAdsResponse instance.
   */
  private void printResponseDetails(
      MutateGoogleAdsRequest request, MutateGoogleAdsResponse response) {
    // Parse the Mutate response to print details about the entities that were removed and/or
    // created in the request.
    for (int i = 0; i  response.getMutateOperationResponsesCount(); i++) {
      MutateOperation operationRequest = request.getMutateOperations(i);
      MutateOperationResponse operationResponse = response.getMutateOperationResponses(i);

      if (operationResponse.getResponseCase()
          != ResponseCase.ASSET_GROUP_LISTING_GROUP_FILTER_RESULT) {
        String entityName = operationResponse.getResponseCase().toString();
        // Trim the substring "_RESULT" from the end of the entity name.
        entityName = entityName.substring(0, entityName.lastIndexOf("_RESULT"));
        System.out.printf("Unsupported entity type: %s%n", entityName);
      }

      String resourceName =
          operationResponse.getAssetGroupListingGroupFilterResult().getResourceName();
      AssetGroupListingGroupFilterOperation assetOperation =
          operationRequest.getAssetGroupListingGroupFilterOperation();

      // Converts the type of operation (for example, "CREATE") to title case.
      String operationTypeString = assetOperation.getOperationCase().toString();
      String operationTypeTitleCase =
          String.form     operationTypeString.substring(0, 1), operationTypeString.substring(1).toLowerCase());

      // Prints information about the completed operation.
      System.out.printf(
          "%sd a(n) AssetGroupListingGroupFilter with resource name: '%s'%n",
          operationTypeTitleCase, resourceName);
    }
  }
}
AddPerformanceMaxProductListingGroupTree.java
      

С#

// Copyright 2022 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 CommandLine;
using Google.Ads.Gax.Examples;
using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V23.Errors;
using Google.Ads.GoogleAds.V23.Resources;
using Google.Ads.GoogleAds.V23.Services;
using Google.Api.Gax;
using System;
using System.Collections.Generic;
using System.Threading;
using static Google.Ads.GoogleAds.V23.Enums.ListingGroupFilterListingSourceEnum.Types;
using static Google.Ads.GoogleAds.V23.Enums.ListingGroupFilterProductConditionEnum.Types;
using static Google.Ads.GoogleAds.V23.Enums.ListingGroupFilterTypeEnum.Types;
using static Google.Ads.GoogleAds.V23.Resources.ListingGroupFilterDimension.Types;

namespace Google.Ads.GoogleAds.Exa<mples.V>23
{
    /// summary
    /// This example shows how to add product partitions to a Performance Max retail campaign.
    ///
    /// For Performance Max campaigns, product partitions are represented using the
    /// AssetGroupListingGroupFilter resource. This resource can be combined with itself to form a
    /// hierarchy that creates a product partition tree.
    ///
    /// For more information about Performance Max retail campaig<ns, see the
    /// see cref="AddPerfo>rmanceMaxRetailCam<paign&qu>ot;/ example.
    /// /summary
    public class AddPerformanceMaxProductListingGroupTree : <Example>Base
    {
        /// summary
        /// Command< line options for running the see
        /// cref="AddPerf>ormanceMaxProductListingGroupTree&<quot;/
 >       /// example.
        /// /summary
        public class Options <: Optio>nsBase
        {
            /// summary
            /// The <Google A>ds customer ID.
            /// /summary
            [Option("customerId", Required = true, HelpText =
                "The Google Ads customer ID.")]
      <      p>ublic long CustomerId { get; set; }

            /// <summary<>/span>
            /// The Asset Group ID.
            /// /summary
            [Option("assetGroupId", Required = true, HelpText =
                "The Asset Group <ID.&quo>t;)]
            public long AssetGroupId { get; set; }

            /// summary
            /// An option to remove the listing group tree< from th>e asset group when this example is
            /// run.
            /// /summary
            [Option("replaceExistingTree", Required = false, HelpText =
                "An option that removes the existing listing group tree from the as<set gro>up.")]
            public bool ReplaceExistingTree { get; set; }
        }

        ///< summary>
        /// <Main method, to r>un this code example as a s<tandal>one application.
        /// /summary
        /// param name="args"The command line arguments./param
        pu<blic st>atic void Main(string[] args)
        {
            Options options = ExampleUtilities.ParseCommandLineOptions(args);

            AddPerformanceMaxProductListingGroupTree codeExample =
                new AddPerformanceMaxProductListingGroupTree();

            Console.WriteLine(codeExample.Description);

            codeExample.Run(
                new GoogleAdsClient(),
                options.CustomerId,
          <      o>ptions.AssetGroupId,
                options.ReplaceExistingTree
      <      );>
        }

        /// summary
        /// R>eturns a description about the code example.
        /// /summary
        public override string Description =
            "This example sh<ows how> to add a product listing group tree to  a " +
            "Performance Max retail campaign.";

        /// summary
        /// A factory that creates MutateOperations for removing an existing tree of
        /// AssetGroupListingGroupFilters.
        ///
        /// AssetGroupListingGroupFilters must be removed in a specific order: all of the children
        /// of a filter must be removed before the filter itself, otherwise the API will return an
        /// error.
        ///
        /// This object is inten<ded to b>e used with an array of MutateOperations to perform a series
        /// of related updates to an AssetGroup.
        /// /summary
        private class AssetG<roupListingGroupFilterRemoveOperatio>nFactory
        {
            private str<ing rootResourc<eName;>>
            private Dictionarystring, AssetGroupListingGroupFilter resources;
            private Dictionarystr<ing, HashSetstring parentsTo>Children;

            public AssetGroupListingGroupFilterRemoveOperationFactory(
                ListAssetGroupListingGroupFilter resources)
            {
                if (resources.Count == 0)
                {
                    throw new I<nvalidOperationException("No li>sting group filters to remove");
                }

  <              t<his.re>>sources = new Dictionarystring, AssetGroupListingGroupFilter();
                this.parentsToChildren = new Dictionarystring, HashSetstring();

                foreach (AssetGroupListingGroupFilter filter in resources)
                {
                    this.resources[filter.ResourceName] = filter;

                    // When the node has no parent, it means it's the root node, which is treated
                    // differently.
                    if (string.IsNullOrEmpty(filter.ParentListingGroupFilter))
                    {
                        if (!string.IsNullOrEmpty(this.rootResourceName))
                        {
                            throw new InvalidOperationException("More than one root node");
                        }

                        this.rootResourceName = filter.ResourceName;
            <      >      continue;
                    }

                    string parentResourceName = filter.ParentListingGroupFilter;

                    HashSetstring siblings;

                    // Check to see if we've already visited a sibling in this group, and fetch or
                    // create a new set as required.
                    if (this.parentsToChildren.ContainsKey(parentResourceName))
                    {
                        siblings = t<his.pa>rentsToChildren[parentResourceName];
                    }
                    else
                    {
                        siblings = new HashSetstring();
                    }

                    s<iblings>.Add(filter.ResourceName);
                    this.parentsToChildren[parentResourceName] = siblings;
                }
            }

            /// summary
            /// C<reates a> list of MutateOp<eration>s that remove all of the r<esources> in the tree
           < /// originally> used to create this factory object.
            /// /summary
            /// returnsA list of MutateOperations/returns
            public< ListMu>tateOperation RemoveAll()
            {
                return this.RemoveDescendentsAndFilter(this.rootResourceName);
            }


            /// summary
            /// Creates a list of MutateOperations that remove all the descendents of the specified
            /// AssetGroupListingGroupFilter resource name. The order of removal is post-order,
<        >    /// where all< the ch>ildren (and their children<, recurs>ively) are removed first<. Then,
       >     /// the node itself is removed.
            /// /summary
            /// retur<nsA list of Mut>ateOperations/returns
<            pub>lic ListMutateOperation RemoveDescendentsAndFilter(string resourceName)
            {
                ListMutateOperatio<n oper>ations = new ListMutateOperation();

                if (this.parentsToChildren.ContainsKey(resourceName))
                {
                    HashSetstring children = this.parentsToChildren[resourceName];

                    foreach (string child in children)
                    {
                        operations.AddRange(this.RemoveDescendentsAndFilter(child));
                    }
                }

                AssetGroupListingGroupFilterOperation operation =
                    new AssetGroupListingGroupFilterOperation()
                    {
                        Remove = resourceName
                    };

                operations.Add(
                    new MutateOperation()
                    {
                        AssetGroupListingGroupFilterOperation = operation
                    }
<       >         );

                return operations;
            }

        }

        private const int TEMPORARY_ID_LISTING_GROUP_ROOT = -1;

        /// summary
        /// A factory that creates MutateOperations wrapping
        /// AssetGroupListingGroupFilterMutateOperations for a specific customerId and
        /// assetGroupId.
     <   ///
 >       /// This object is intended to be used with an array of MutateOperations to perform an
        /// atomic update to an AssetGroup.
        /// /summary
        private class AssetGroupListingGroupFilterCreateOperationFactory
        {
            private long customerId;
            private long assetGroupId;
            private long rootListingGroupId;
            private long nextId;

            public AssetGroupListingGroupFilterCreateOperationFactory(
                long customerId,
                long assetGroupId,
                long rootListingGroupId)
            {
                this.customerId = customerId;
                this.assetGroupId = assetGro<upId;
 >               this.rootListingGroupId = rootListingGroupId;
                this.nextId = this.rootListingGroupId - 1;
            }

            /// summary
            /// Returns a new temporary ID to be used for a resource name in a MutateOperation. See
            /// https:<//develo>pers.google.com/g<oogle-a>ds/api/docs/mutatin<g/best-p>ractices#temporary_resource_names
            /// for details about temporary IDs.
            /// /summary
            /// returnsA new temporary ID./returns
            public long NextId<()
    >        {
                long i = nextId;
                Interlocked.Decrement(ref nextId);
                return i;
            }

            /// summary
            /// Creates a MutateOperation that creates a root AssetGroupListingGroupFilter for the
         <   /// f>actory's Asse<tGroup.>
            ///
<        >    /// The root node or partition is the default, which is displayed as "All Products".
            /// /summary
            /// returnsA MutateOperation/returns
            public MutateOperation CreateRoot()
            {
                AssetGroupListingGroupFilter listingGroupFilter = new AssetGroupListingGroupFilter()
                {
                    ResourceName = ResourceNames.AssetGroupListingGroupFilter(
                        this.customerId,
                        this.assetGroupId,
                        this.rootListingGroupId
                    ),

                    AssetGroup = ResourceNames.AssetGroup(
                        this.customerId,
                        this.assetGroupId
                    ),

                    // Since this is the root node, do not set the ParentListingGroupFilter. For all
     <               // >other nodes, this would refer to the parent listing group filter resource
                    // name.
                    // ParentListingGroupFilter = "PARENT FILTER NAME"

                    // Unlike AddPerformanceMaxRetailCampaign, the type for the root node here must
                    // be Subdivision because we add child partitions under it.
                    Type = ListingGroupFilterType.Subdivision,

                    // Because this is a Performance Max campaign for retail, we need to specify
                    // that this is in the shopping listing source.
                    ListingSource = ListingGroupFilterListingSource.Shopping
                };

                AssetGroupListingGroupFilterOperation operation =
                    new AssetGroupListingGroupFilterOperation()
                    {
                        Create = listingGroupFilter
                    };

  <       >       return new MutateOperation()
                {
                    AssetGroupListingGroupFilterOperation = operation
                };
            }


            /// summary
            /// Creates a MutateOperation that creates a intermediate AssetGroupListingGroupFilter
            /// for t<he facto>ry's AssetGro<up.
            ///>
            /// Use this method if the filter wil<l have> child filters. O<therwise, use t>he
            /// CreateUnit method.
            /// /summary
            /<// par>am name="par<ent"The ID of the> parent AssetGroupListingGroupFilter./param
            /// param name="id&q<uot;Th>e ID of AssetGrou<pListin>gGroupFilter that< will be>
            /// created./param
            /// param name="dimension"The dimension to associate with the
            /// AssetGroupListingGroupFilter./param
            /// returnsA MutateOperation/returns
            public MutateOperation CreateSubdivision(
                long parent,
                long id,
                ListingGroupFilterDimension dimension)
            {
                AssetGroupListingGroupFilter listingGroupFilter = new AssetGroupListingGroupFilter()
                {
                    ResourceName = ResourceNames.AssetGroupListingGroupFilter(
                        this.customerId,
                        this.assetGroupId,
                        id
                    ),

                    AssetGroup = ResourceNames.AssetGroup(
                        this.customerId,
                        this.assetGroupId
                    ),

                    ParentListingGroupFilter = ResourceNames.AssetGroupListingGroupFilter(
                        this.customerId,
                        this.assetGroupId,
                        parent
                    ),

                    // We must use the Subdivision type to indicate that the
                    // AssetGroupListingGroupFilter will have children.
                    Type = ListingGroupFilterType.Subdivision,

                    // Because this is a Performance Max campaign for retail, we need to specify
                    // that this is in the shopping listing source.
                    ListingSource = ListingGroupFilterListingSource.Shopping,

                    CaseValue = dimension
                };

                AssetGroupListingGroupFilterOperation filterOperation =
                    new AssetGroupListingGroupFilterOperation()
                    {
                        Create = listingGroupFilte<r
     >               };

                return new MutateOperation()
                {
                    AssetGroupListingGroupFilterOperation = filterOperation
                };
            }


            /// summary
            /// Creates a MutateOperation that creates a child AssetGroupListingGroupFi<lter
   >         /// for <the factory's A>ssetGroup.
            ///
            /// Use thi<s meth>od if the filter <won't have >child filters. Otherwise, use the
            /// CreateSubdivision method.
<      >      /// /summar<y
            /// para>m name="parent"The ID of the parent AssetGroupListingGroupFilter./param<
     >       /// param <name=&q>uot;id"The I<D of Ass>etGroupListingGroupFilter that will be
            /// created./param
            /// param name="dimension"The dimension to associate with the
            /// AssetGroupListingGroupFilter./param
            /// returnsA MutateOperation/returns
            public MutateOperation CreateUnit(
                long parent,
                long id,
                ListingGroupFilterDimension dimension)
            {
                AssetGroupListingGroupFilter listingGroupFilter = new AssetGroupListingGroupFilter()
                {
                    ResourceName = ResourceNames.AssetGroupListingGroupFilter(
                        this.customerId,
                        this.assetGroupId,
                        id
                    ),

                    AssetGroup = ResourceNames.AssetGroup(
                        this.customerId,
                        this.assetGroupId
                    ),

                    ParentListingGroupFilter = ResourceNames.AssetGroupListingGroupFilter(
                        this.customerId,
                        this.assetGroupId,
                        parent
                    ),

                    // We must use the UnitIncluded type to indicate that the
                    // AssetGroupListingGroupFilter won't have children.
                    Type = ListingGroupFilterType.UnitIncluded,

                    // Because this is a Performance Max campaign for retail, we need to specify
                    // that this is in the shopping listing source.
                    ListingSource = ListingGroupFilterListingSource.Shopping,

                    CaseValue = dimension
                };

                AssetGroupListingGroupFilterOperation filterOperation =
                    new AssetGroupListingGroupFilterOperation()
                    {
              <       >   Create = listingGroupFilter
                 <   };

 >             <  return new Mutate>Operation()
          <      >{
           <         AssetGroupList>ingGroupFilterOperation = f<ilterO>peration
    <            };
          >  }

        }

        /// summary
        /// Runs< the c>ode example.
<        /// /summary
        ///> param name="client"The Google Ads client./param
        /// param name=<">customerId"The Google Ads customer ID./param
        /// param name="assetGroupId"The asset group id for the Performance Max campaign./param
        /// param name="replaceExistingTree"Option to remove existing product tree
        /// from the passed in asset group./param
        public void Run(
            GoogleAdsClient client,
            long customerId,
            long assetGroupId,
            bool replaceExistingTree)
        {
            GoogleAdsServiceClient googleAdsServiceClient =
                client.GetService(Services.V23.GoogleAdsService);

            string assetGroupResourceName = ResourceNames.AssetGroup(customerId, assetGroupId);

            // We use a factory to create all the MutateOperations that manipulate a specific
            // AssetGroup for a specific customer. The operations returned by the factory's methods
            // are used to optionally remove all AssetGroupListingGroupFilters from the tree, and
            // then to construct a new tree of filters. These filters can have a parent-child
            // relationship, and also include a special root that includes all children.
            //
            // When creating these filters, we use temporary IDs to create the hierarchy between
            // the root listing group filter, and the subdivisions and leave nodes beneath that.
            //
            // The factory specific to a customerId and assetGroupId is created below.
            AssetGroupListingGroupFilterCreateOperationFactory createOperationFactory =
                new AssetGroupListingGroupFilterCreateOperationFactory(
                    customerId,
                    assetGro<upId,
                    TE>MPORARY_ID_LISTING_GROUP_ROOT
                );

            MutateGoogleAdsRequest request = new MutateGoogleAdsRequest
            {
                CustomerId = customerId.ToString()
            };

            if (replaceExistingTree)
            {
                ListAssetGroupListingGroup>Filter existingListingGroupFilters =
                    GetAllExistingListingGroupFilterAssetsInAssetGroup(
                        client,
                        customerId,
                        assetGroupResourceName
                    );

                if (existingListingGroupFilters.Count  0)
                {
                    // A special factory object that ensures the creation of remove operations in the
                    // correct order (child listing group filters must be removed before their parents).
                    AssetGroupListingGroupFilterRemoveOperationFactory removeOperationFactory =
                        new AssetGroupListingGroupFilterRemoveOperationFactory(
                            existingListingGroupFilters
                        );

                    request.MutateOperations.AddRange(removeOperationFactory.RemoveAll());
                }
            }

            request.MutateOperations.Add(createOperationFactory.CreateRoot());

            request.MutateOperations.Add(
                createOperationFactory.CreateUnit(
                    TEMPORARY_ID_LISTING_GROUP_ROOT,
                    createOperationFactory.NextId(),
                    new ListingGroupFilterDimension()
                    {
                        ProductCondition = new ListingGroupFilterDimension.Types.ProductCondition()
                        {
                            Condition = ListingGroupFilterProductCondition.New
                        }
                    }
                )
            );

            request.MutateOperations.Add(
                createOperationFactory.CreateUnit(
                    TEMPORARY_ID_LISTING_GROUP_ROOT,
                    createOperationFactory.NextId(),
                    new ListingGroupFilterDimension()
                    {
                        ProductCondition = new ListingGroupFilterDimension.Types.ProductCondition()
                        {
                            Condition = ListingGroupFilterProductCondition.Used
                        }
                    }
                )
            );

            // We save this ID because create child nodes underneath it.
            long subdivisionIdConditionOther = createOperationFactory.NextId();

            request.MutateOperations.Add(
                // We're calling CreateSubdivision because this listing group will have children.
                createOperationFactory.CreateSubdivision(
                    TEMPORARY_ID_LISTING_GROUP_ROOT,
                    subdivisionIdConditionOther,
                    new ListingGroupFilterDimension()
                    {
                        // All sibling nodes must have the same dimension type. We use an empty
                        // ProductCondition to indicate that this is an "Other" partition.
                        ProductCondition = new ListingGroupFilterDimension.Types.ProductCondition()
                    }
                )
            );

            request.MutateOperations.Add(
                createOperationFactory.CreateUnit(
                    subdivisionIdConditionOther,
                    createOperationFactory.NextId(),
                    new ListingGroupFilterDimension()
                    {
                        ProductBrand = new ProductBrand()
                        {
                            Value = "CoolBrand"
                        }
                    }
                )
            );

            request.MutateOperations.Add(
                createOperationFactory.CreateUnit(
                    subdivisionIdConditionOther,
                    createOperationFactory.NextId(),
                    new ListingGroupFilterDimension()
                    {
                        ProductBrand = new ProductBrand()
                        {
                            Value = "CheapBrand"
                        }
                    }
                )
            );

            request.MutateOperations.Add(
                createOperationFactory.CreateUnit(
                    subdivisionIdConditionOther,
                    createOperationFactory.NextId(),
                   < new Li>stingGroupFilterDimension()
                    {
                        ProductBran<d = new >ProductBrand(<)
                 >   }
                )<
     >       );

  <          MutateGoogleA>dsResponse response = googl<eAdsSe>rviceClient.M<utate(request);

            PrintR>esponseDetails(request, response);
  <      >}


        /<// summ>ary
        /// Fetches all of the listing grou<p filter>s in an asset group.
<        /// /summary
       > /// param name="client"The Google Ads Client./param
        /// param name="customerId"The Google Ads customer ID./param
        /// param name="assetGroupResourceName"The resource< name of the asset group./pa>ram
        /// retur<nsA list of asset group list>ing filter resources./returns
        private ListAssetGroupListingGroupFilter
            GetAllExistingListingGroupFilterAssetsInAssetGroup(
                GoogleAdsClient client,
                long customerId,
                string assetGroupResourceName)
        {
            ListAssetGroupListingGroupFilter resources = new ListAssetGroupListingGroupFilter();

            // Get the GoogleAdsService.
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V23.GoogleAdsService);

            SearchGoogleAdsRequest request = new SearchGoogleAdsRequest()
            {
                CustomerId = customerId.ToString(),
                Query =
                    $@"
                    SELECT
                        asset_group_listing_group_filter.resource_name,
                        asset_group_listing_group_filter.parent_listing_group_filter
                    FROM asset_group_listing_group_filter
                    WHERE
                        asset_group_listing_group_filter.asset_group = '{assetGroupResourceName}'
                    &q<uot;
            };

            // T>he below enumerable will automatically iterate through the pages of the search
            // request. The limit to the number of listing group filters permitted in a Performance
            // Max campaign can be found here:
            // https://developers.google.com/google-ads/ap<i/docs/>best-practices/system-limits
            PagedEnumerableSearchGoogleAdsResponse, GoogleAdsRow searchPagedResponse =
                googleAdsService.Search(request);

            foreach (G<oogleAds>Row row in se<archPagedResponse)
 >           {
                resou<rces.A>dd(row.AssetG<roupListingGroupFilte>r);
            }

            retu<rn res>ources;
        }


        /// summary
        /// Prints the details of a MutateGoogleAdsResponse. Parses the "response" oneof field name
        /// and uses it to extract the new entity's name and resource name.
        /// /summary
        /// param name=&<quot;request"A MutateGoogleAdsRequest instance./param
        /// param name="response"A MutateGoogleAdsResponse instance./param
        private void PrintResponseDetails(MutateGoogleAdsRequest request, MutateGoogleAdsResponse response)
        {
            // Parse the Mutate response to print details about the entities that were created
            // in the request.
            for (int i = 0; i  response.MutateOperationResponses.Count; i++)
            {
                MutateOperation operationRequest = request.MutateOperations[i];
                MutateOperationResponse operationResponse = response.MutateOperationResponses[i];

                if (operationResponse.ResponseCase != MutateOperationResponse.ResponseOneofCase.AssetGroupListingGroupFilterResult)
                {
                    string entityName = operationResponse.ResponseCase.ToString();
                    // Trim the substring "Result" from the end of the entity name.
                    entityName = entityName.Remove(entityName.Length - 6);

                    Console.WriteLine($"Unsupported entity type: {entityName}");
                }

                string resourceName = operationResponse.AssetGroupListingGroupFilterResult.ResourceName;
                AssetGroupListingGroupFilterOperation assetOperation = operationRequest.AssetGroupListingGroupFilterOperation;

                switch (assetOperation.OperationCase)
                {
                    case AssetGroupListingGroupFilterOperation.OperationOneofCase.Create:
                        Console.WriteLine(
                            $"Created a(n) AssetGroupListingGroupFilter with resource name: '{resourceName}'.");
                        break;

                    case AssetGroupListingGroupFilte                   Console.WriteLine(
                            $"Removed a(n) AssetGroupListingGroupFilter with resource name: '{resourceName}'.");
                        break;

                    default:
                        Console.WriteLine($"Unsupported operation type: {assetOperation.OperationCase.ToString()}");
                        continue;
                }
            }
        }
    }
}
AddPerformanceMaxProductListingGroupTree.cs
      

PHP

<?php

/**
 * Copyright 2022 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\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V23\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V23\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V23\GoogleAdsException;
use Google\Ads\GoogleAds\Util\V23\ResourceNames;
use Google\Ads\GoogleAds\V23\Enums\ListingGroupFilterListingSourceEnum\ListingGroupFilterListingSource;
use Google\Ads\GoogleAds\V23\Enums\ListingGroupFilterProductConditionEnum\ListingGroupFilterProductCondition;
use Google\Ads\GoogleAds\V23\Enums\ListingGroupFilterTypeEnum\ListingGroupFilterType;
use Google\Ads\GoogleAds\V23\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V23\Resources\AssetGroupListingGroupFilter;
use Google\Ads\GoogleAds\V23\Resources\ListingGroupFilterDimension;
use Google\Ads\GoogleAds\V23\Resources\ListingGroupFilterDimension\ProductBrand;
use Google\Ads\GoogleAds\V23\Resources\ListingGroupFilterDimension\ProductCondition;
use Google\Ads\GoogleAds\V23\Services\AssetGroupListingGroupFilterOperation;
use Google\Ads\GoogleAds\V23\Services\GoogleAdsRow;
use Google\Ads\GoogleAds\V23\Services\MutateGoogleAdsRequest;
use Google\Ads\GoogleAds\V23\Services\MutateGoogleAdsResponse;
use Google\Ads\GoogleAds\V23\Services\MutateOperation;
use Google\Ads\GoogleAds\V23\Services\MutateOperationResponse;
use Google\Ads\GoogleAds\V23\Services\SearchGoogleAdsRequest;
use Google\ApiCore\ApiException;
use Google\ApiCore\Serializer;

/**
 * This example shows how to add product partitions to a Performance Max retail campaign.
 *
 * For Performance Max campaigns, product partitions are represented using the
 * AssetGroupListingGroupFilter resource. This resource can be combined with itself to form a
 * hierarchy that creates a product partition tree.
 *
 * For more information about Performance Max retail campaigns, see the
 * AddPerformanceMaxRetailCampaign example.
 */
class AddPerformanceMaxProductListingGroupTree
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const ASSET_GROUP_ID = 'INSERT_ASSET_GROUP_ID_HERE';
    // Optional: Removes the existing listing group tree from the asset group or not.
    //
    // If the current asset group already has a tree of listing group filters, and you
    // try to add a new set of listing group filters including a root filter, you'll
    // receive a 'ASSET_GROUP_LISTING_GROUP_FILTER_ERROR_MULTIPLE_ROOTS' error.
    //
    // Setting this option to true will remove the existing tree and prevent this error.
    private const REPLACE_EXISTING_TREE = false;

    // We specify temporary IDs that are specific to a single mutate request.
    // Temporary IDs are always negative and unique within one mutate request.
    private const LISTING_GROUP_ROOT_TEMPORARY_ID = -1;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the con>stants above.
        $options = (new ArgumentParser())-parseCom>mandArguments([
            ArgumentNames::CUSTOMER_ID = GetOpt::REQUIR>ED_ARGUMENT,
            ArgumentNames::ASSET_GROUP_ID = GetOpt::REQUIRED_ARGU>MENT,
            ArgumentNames::REPLACE_EXISTING_TREE = GetOpt::OPTIONAL_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.>
        $o>Auth2Credential = (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::ASSET_GROUP_ID] ?: self::ASSET_GROUP_ID,
                filter_var(
                    $options[ArgumentNames::REPLACE_EXISTING_TREE]
                        ?: self::REPLACE_EXISTING_TREE,
                    FILTER_VALIDATE_BOOLEAN
                )
            );
        } 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 $assetGroupId the asset group ID
     * @param bool $replaceExistingTree true if it should replace the existing listing group
     *     tree on the asset group
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        int $assetGroupId,
        bool $replaceExistingTree
    ) {
        // We create all the mutate operations that manipulate a specific asset group for a specific
        // customer. The operations are used to optionally remove all asset group listing group
        // filters from the tree, and then to construct a new tree of filters. These filters can
        // have a parent-child relationship, and also include a special root that includes all
        // children.
        //
        // When creating these filters, we use temporary IDs to create the hierarchy between
        // the root listing group filter, and the subdivisions and leave nodes beneath that.
        $mutateOperations = [];
        if ($replaceExistingTree === true) {
            $existingListingGroupFilters = self::getAllExistingListingGroupFilterAssetsInAssetGroup(
                $googleAdsClient,
                $customerId,
                ResourceNames::forAsse>tGroup($customerId, $assetGroupId)
            );
            if (count($existingListingGroupFilters)  0) {
                $mutateOperations = array_merge(
                    $mutateOperations,
                    // Ensures the creation of remove operations in the correct order (child listing
                    // group filters must be removed before their parents).
                    self::createMutateOperationsForRemovingListingGroupFiltersTree(
                        $existingListingGroupFilters
                    )
                );
            }
        }

        $mutateOperations[] = self::createMutateOperationForRoot(
            $customerId,
            $assetGroupId,
            self::LISTING_GROUP_ROOT_TEMPORARY_ID
        );

        // The temporary ID to be used for creating subdivisions and units.
        static $tempId = self::LISTING_GROUP_ROOT_TEMPORARY_ID - 1;

        $mutateOperations[] = self::createMutateOperationForUnit(
            $customerId,
            $assetGroupId,
            $tempId--,
            self::LISTING_GROUP_>ROOT_TEMPORARY_ID,
            new ListingGroupFilterDime>nsion([
                'product_condition' = new ProductCondition([
                    'condition' = ListingGroupFilterProductCondition::PBNEW
                ])
            ])
        );

        $mutateOperations[] = self::createMutateOperationForUnit(
            $customerId,
            $assetGroupId,
            $tempId--,
            self>::LISTING_GROUP_ROOT_TEMPORARY_ID,
            new Listin>gGroupFilterDimension([
                'product_condition' = new ProductCondition([
                    'condition' = ListingGroupFilterProductCondition::USED
                ])
            ])
        );

        // We save this ID to create child nodes underneath it.
        $conditionOtherSubdivisionId = $tempId--;

        // We're calling createMutateOperationForSubdivision() because this listing group will
        // have children.
        $mutateOperations[] = self::createMutateOperationForSubdivision(
            $customerId,
            $assetGroupId,
            $conditionOtherSubdivisionId,
            self::LISTING_GROUP_ROOT_TEMPORARY_ID,
            new ListingGroupFilterDimension([
                // All sibling nodes must have the same dimension type>. We use an empty
                // ProductCondition to indicate that this is an "Other" partition.
                'product_condition' = new ProductCondition()
            ])
        );

        $mutateOperations[] = self::createMutateOperationForUnit(
            $customerId,
            $as>setGroupId,
            $tem>pId--,
            $conditionOtherSubdivisionId,
            new ListingGroupFilterDimension(
                ['product_brand' = new ProductBrand(['value' = 'CoolBrand'])]
            )
        );

        $mutateOperations[] = self::createMutateOperationForUnit(
            $cus>tomerId,
            $assetG>roupId,
            $tempId--,
            $conditionOtherSubdivisionId,
            new ListingGroupFilterDimension([
                'product_brand' = new ProductBrand(['value' = 'CheapBrand'])
            ])
        );

        $mutateOperations[] = self::createMutateOperationForUnit(
            $cus>tomerId,
            $assetGroupId,
            $tempId--,
            $conditionOtherSubdivisionId,
            // All other product brands.
            new ListingGrou>pFilterDimension(['product_brand' = new ProductBrand()])
        >);

        // Issues a mutate request to create everything and prints its information.
        $googleAdsServiceClient = $googleAdsClient-getGoogleAdsServiceClient();
        $response = $googleAdsServiceClient-mutate(
            MutateGoogleAdsRequest::build($customerId, $mutateOperations)
        );

        self::printResponseDetails($mutateOperations, $response);
    }

    /**
     * Fetches all of the asset group listing group filters in an asset group.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the customer ID
     * @param string $assetGroupResourceName the resource name of the asset group
     * @return AssetGroupListingGroupFilter[] the list of asset group listing group filters
     */
    private static function getAllExisti>ngListingGroupFilterAssetsInAssetGroup(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $assetGroupResourceName
    ): array {
        $googleAdsServiceClient = $googleAdsClient-getGoogleAdsServiceClient();
        // Creates a query that retrieves asset group listing group filters.
        // The limit to the number of listing group filters permitted in a Performance
        // Max campaign can be found here:
        // https://developers.google.com/google-ads/api/docs/best-practices/system-limits.
        $query = sprintf(
            'SELECT asset_group_listing_group_filter.resource_name, '
            . 'asset_group_listing_group_filter.parent_listing_group_filter '
            . 'FROM asset_group_listin>g_group_filter '
            . 'WHERE asset_group_listing_group_filter.asset_group = "%s"',
            $assetGroupResourceName
        );

        // Issues a search request.
        $response =
      >      $googleAdsServiceClient-search(SearchGoogleAdsRequest::build($customerId, $query));

        $assetGroupListingGroupFilters = [];
        // Iterat>es over all rows in all pages to get an asset group listing group filter.
        foreach ($response-iterateAllElements() as $googleAdsRow) {
            /** @var GoogleAdsRow $googleAdsRow */
            $assetGroupListingGroupFilters[] = $googleAdsRow-getAssetGroupListingGroupFilter();
        }
        return $assetGroupListingGroupFilters;
    }

    /**
     * Creates mutate operations for removing an existing tree of asset group listing group filters.
     *
     * Asset group listing group filters must be removed in a specific order: all of the children
     * of a filter must be removed before the filter itself, otherwise the API will return an
     * error.
     *
     * @param AssetGroupListingGroupFilter[] $assetGroupListingGroupFilters the existing asset
     *     group listing group filters
     * @return MutateOperation[] the list of MutateOperations to remove all listing groups
     */
    private static function createMutateOperationsForRemovingListingGroupFiltersTree(
        array $assetGroupListingGroupFilters
    ): array {
        if (empty($assetGroupListingGroupFilters)) {
            throw new \UnexpectedValueException('No listing group filters to remove');
        }

        $resource>NamesToListingGroupFilters = [];
        $parentsToChildren = [];
        $rootResourceName = null;
        foreach ($assetGroupListingGroupFilters as $assetGroupListingGroupFilter) {
            $resourceNamesToListingGroupFilters[$asset>GroupListingGroupFilter-getResourceName()] =
                $assetGroupListingGroupFilter;
            // When the node has no parent, it means it's the root node, which is treated
            // differently.
            if (empty($assetGroupListingGroupF>ilter-getParentListingGroupFilter())) {
                if (!is_null($rootResourceName)) {
                    throw new \Un>expectedValueException('More than one root node found.');
                }
                $rootResourceName = $assetGroupListingGroupFilter-getResourceName();
                continue;
            }

            $parentResourceName = $assetGroupListingGroupFilter-getParentListingGroupFilter();
            $siblings = [];

            // Checks to see if we&#>39;ve already visited a sibling in this group and fetches it.
            if (array_key_exists($parentResourceName, $parentsToChildren)) {
                $siblings = $parentsToChildren[$parentResourceName];
            }
            $siblings[] = $assetGroupListingGroupFilter-getResourceName();
            $parentsToChildren[$parentResourceName] = $siblings;
        }

        return self::createMutateOperationsForRemovingDescendents(
            $rootResourceName,
            $parentsToChildren
        );
    }

    /**
     * Creates a list of mutate operations that remove all the descendents of the specified
     * asset group listing group filter's resource name. The order of removal is post-order,
     * where all the children (and their children, recursively) are removed first. Then,
     * the node itself is removed.
     *
     * @param string $assetGroupListingGroupFilterResourceName the resource name of the root of
     *     listing group tree
     * @param array $parentsToChildren the map from parent resource names to children resource
     *     names
     * @return MutateOperation[] the list of MutateOperations to remove all listing groups
     */
    private static function createMutateOperationsForRemovingDescendents(
        string $assetGroupListingGroupFilterResourceName,
        array $parentsToChildren
    ): array {
        $operations = [];
        if (array_key_exists($assetGroupListingGroupFilterResourceName, $parentsToChildren)) {
            foreach ($parentsToChildren[$assetGroupListingGroupFilterResourceName] as $child) {
                $operations = array_merge(
              >      $operations,
                    self::createMutateOperationsForRemov>ingDescendents($child, $parentsToChildren)
                );
            }
        }

        $operations[] = new MutateOperation([
            'asset_group_listing_group_filter_operation'
                = new AssetGroupListingGroupFilterOperation([
                    'remove' = $assetGroupListingGroupFilterResourceName
                ])
        ]);
        return $operations;
    }

    /**
     * Creates a mutate operation that creates a root asset group listing group filter for the
     * factory's asset group.
     *
     * The root node or partition is the default, which is displayed as "All Products".
     *
     * @param int $customerId the customer ID
     * @param int $assetGroupId the asset group ID
     * @param int $rootListingGroupId the root listing group ID
     * @return MutateOperation the mutate> operation for creating the root
     */
    private static function createMutateOperationForRoot(
        int $customerId,
        int $assetGroupId,
        int $rootListingGroupId
    >): MutateOperation {
        $assetGroupListingGroupFilter = new AssetGroupListingGroupFilter([
            'resource_name' = ResourceNames::forAssetGroupListingGroupFilter(
                $customerId,
                $assetGroupId,
                $rootListingGroupId
            ),
            'asset_group' = ResourceNames::forAssetGroup($customerId, $assetGroupId),
            // Since this is the root node, do not set the 'parent_>listing_group_filter' field. For
            // all other nodes, this would refer to the parent listing group filter resource
            // name.

            // Unlike AddPerformanceMaxRetailCampaign, the type f>or the root node here must
            // be SUBDIVISION because we add child partitions under it.
            'type' = ListingGroupFilterType::SUBDIVISION,
  >          // Because this is a Performance Max campaign for retail, we need> to specify
            // that this is in the shopping listing source.
            'listing_source' = ListingGroupFilterListingSource::SHOPPING
        ]);

        return new MutateOperation([
            'asset_group_listing_group_filter_operation'
                = new AssetGroupListingGroupFilterOperation([
                    'create' = $assetGroupListingGroupFilter
                ])
        ]);
    }

    /**
     * Creates a mutate operation that creates a intermediate asset group listing group filter.
     *
     * @param int $customerId the customer ID
     * @param int $assetGroupId the asset group ID
     * @param int $assetGroupListingGroupFilterId the ID of the asset group listing group filter to
     *     be created
     * @param int $parentId the ID of the parent of asset group listing group filter to be created
     * @param ListingGroupFilterDimension $listingGroupFilterDimension the listing group
     *     filter dimension to associate with the asset group listing group filter
     * @return MutateOperation the mutate operation for creating a subdivision
     */
    private> static function createMutateOperationForSubdivision(
        int $customerId,
        int $assetGroupId,
        int $assetGroupListingGroupFilterId,
        int $parentId,
        ListingGroupFilte>rDimension $listingGroupFilterDimension
    ): MutateOperation {
        $assetGroupListingGroupFilter = new AssetGroupListingGroupFilter([
            'resource_name' = ResourceNames::forAssetGrou>pListingGroupFilter(
                $customerId,
                $assetGroupId,
                $assetGroupListingGroupFilterId
            ),
            'asset_group' = ResourceNames::forAssetGroup($custome>rId, $assetGroupId),
            // Sets the type as a SUBDIVISION, which will allow th>e node to be the parent of
            // another sub-tree.
            'type' = ListingGroupFilterType::SUBDIVISION,
            // Because this is a Performance Max campaign for retail, we need to specify
            // that this is in the sh>opping listing source.
            'listing_source' = ListingGroupFilterListingSource::SHOPPING,
            'parent_listing_group_filter'> = ResourceNames::forAssetGroupListingGroupFilter(
                $custome>rId,
                $assetGroupId,
                $parentId
            ),
            // Case values contain the listing dimension used for the node.
            'case_value' = $listingGroupFilterDimension
        ]);

        return new MutateOperation([
            'asset_group_listing_group_filter_operation'
                = new AssetGroupListingGroupFilterOperation([
                    'create' = $assetGroupListingGroupFilter
                ])
        ]);
    }

    /**
     * Creates a mutate operation that creates a child asset group listing group filter (unit
     * node).
     *
     * Use this method if the filter won't have child filters. Otherwise, use
     * createMutateOperationForSubdivision().
     *
     * @param int $customerId the customer ID
     * @param int $assetGroupId the asset group ID
     * @param int $assetGroupListingGroupFilterId the ID of the asset group listing group filter to
     *     be created
     * @param int $parentId the ID of the parent of asset group listing group filter to be
     *      created
     * @param ListingGroupFilterDimension $listingGroupFilterDimension the listing group
     *     filter dimension to associate with the asset group listing group filter
     * @return Muta>teOperation the mutate operation for creating a unit
     */
    private static function createMutateOperationForUnit(
        int $customerId,
        int $assetGroupId,
        int $assetGroupListi>ngGroupFilterId,
        string $parentId,
        ListingGroupFilterDimension $listingGroupFilterDime>nsion
    ): MutateOperation {
        $assetGroupListingGroupFilter = new AssetGroupListingGroupFilter([
            'resource_name' = ResourceNames::forAssetGroupListingGroupFilter(
                $customerId,
                $assetGroupId,
                $assetGroupListingGroupFilterId
         >   ),
            'asset_group' = ResourceNames::forAssetGroup($customerId, $assetGroupId),
            'parent_listing_group_filter' = ResourceNames::forAssetGroupListingGroupFilter(
                $cu>stomerId,
                $assetGroupId,
                $parentId
   >         ),
            // Sets the type as a UNIT_INCLUDED to indicate that this asset group listing group
            // filter won't have children.>
            'type' = ListingGroupFilterType::UNIT_INCLUDED,
      >      // Because this is a Performance Max campaign for retail, we need to specify
            // that this is in the shopping listing source.
            'listing_source' = ListingGroupFilterListingSource::SHOPPING,
            'case_value' = $listingGroupFilterDimension
        ]);

        return new MutateOperation([
            'asset_group_listing_group_filter_operation'
                = new AssetGroupListingGroupFilterOperation([
                    'create' = $assetGroupListingGroupFilter
                ])
        ]);
    }

    /**
     * Prints the details of a mutate google ads response. Parses> the "response" oneof field> name
     * and uses it to extract the new entity's name and resource name.
     *
     * @param MutateOperation[] $mutateOperations the submitted> mutate operations
     * @param MutateGoogleAdsResponse $mutateGoogleAdsResponse the mutate Google Ads response
     */
    private static function printResponseDetails(
        array $mutateOperations,
        MutateGoogleAdsResponse $mutateGoogleAdsResponse
    ): void {
        foreach (
         >   $mutateGoogleAdsResponse-getMutateOperationResponses() as $i = $operationResponse
        ) {
            /** @var MutateOperationResponse $operationResponse */
          >  if (
                $operationResponse-getResponse()
                    !== 'asset_group_listing_gr>oup_filter_result'
            ) {
        >        // Trims the substring "_result" from the end of the entity name.
                printf(
                    "Unsupported entity type: %s.%s",
                    substr($operationResponse-getResponse(), 0, -strlen('_>result'>;)),
                    PHP_EOL
                );
                continue;
            }

            $operation = $mutateOperations[$i]-getAssetGroupListingGroupFilterOperation();
            $getter = Serializer::getGetter($operationResponse-getResponse());
            switch ($operation-getOperation()) {
                case> 'crea>te':
                    printf(
                        "Created an asset group listing group filter with resource name: "
                         . " '%s'.%s",
                        $operationResponse-$getter()-getR>esourceName(),
                        PHP_EOL
                    );
                    break;
                case 'remove':
                  ved an asset group listing group filter with resource name: "
                        . " '%s'.%s",
                        $operationResponse-$getter()-getResourceName(),
                        PHP_EOL
                    );
                    break;
                default:
                    printf(
                        "Unsupported operation type: '%s'.%s",
                        $operation-getOperation(),
                        PHP_EOL
                    );
            }
        }
    }
}

AddPerformanceMaxProductListingGroupTree::main();
AddPerformanceMaxProductListingGroupTree.php
      

Питон

#!/usr/bin/env python
# Copyright 2022 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.
"""Shows how to add product partitions to a Performance Max retail campaign.

For Performance Max campaigns, product partitions are represented using the
AssetGroupListingGroupFilter resource. This resource can be combined with
itself to form a hierarchy that creates a product partition tree.

For more information about Performance Max retail campaigns, see the
shopping_ads/add_performance_max_retail_campaign.py example.
"""


import argparse
import sys
from typing import Dict, List, Optional

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
from google.ads.googleads.v23.resources.types.asset_group_listing_group_filter import (
    ListingGroupFilterDimension,
)
from google.ads.googleads.v23.resources.types.asset_group_listing_group_filter import (
    AssetGroupListingGroupFilter,
)
from google.ads.googleads.v23.services.services.google_ads_service import (
    GoogleAdsServiceClient,
)
from google.ads.googleads.v23.services.types.google_ads_service import (
    MutateGoogleAdsResponse,
    SearchGoogleAdsRequest,
    SearchGoogleAdsResponse,
)
from google.ads.googleads.v23.services.types.google_ads_service import (
    MutateOperation,
)


# We specify temporary IDs that are specific to a single mutate request.
# Temporary IDs are always negative and unique within one mutate request.
#
# See https://developers.google.com/google-ads/api/docs/mutating/best-practices
# for further details.
#
# These temporary IDs are fixed because they are used in multiple places.
_TEMPORARY_ID_LISTING_GROUP_ROOT: int = -1


class AssetGroupListingGroupFilterRemoveOperationFactory:
    def __init__(
        self,
        client: GoogleAdsClient,
        listing_group_filters: List[AssetGroupListingGroupFilter],
    ):
        """Factory class for creating sorted list of MutateOperations.

        The operations remove the given tree of AssetGroupListingGroupFilters,
        When removing these listing group filters, the remove operations must be
        sent in a specific order that removes leaf nodes before removing parent
        nodes.

        Args:
            client: an initialized GoogleAdsClient instance.
            listing_group_filters: a list of AssetGroupListingGroupFilters.
        """
        if not listing_group_filters:
            raise ValueError("No listing group filters to remove.")

        self.client: GoogleAdsClient = client
        self.root_resource_name: Optional[str] = None
        self.parents_to_children: Dict[str, List[str]] = {}

        # Process the given list of listing group filters to identify the root
        # node and any parent to child edges in the tree.
        for listing_group_filter_node in listing_group_filters:
            resource_name: str = listing_group_filter_node.resource_name
            parent_resource_name: Optional[str] = (
                listing_group_filter_node.parent_listing_group_filter
            )

            # When the node has no parent, it means it's the root node.
            if not parent_resource_name:
                if self.root_resource_name:
                    # Check if another root node has already been detected and
                    # raise an error if so, as only one root node can exist for
                    # a given tree.
                    raise ValueError("More than one listing group parent node.")
                else:
                    self.root_resource_name = resource_name
            else:
                # Check if we've already visited a sibling in this group, and
                # either update it or create a new branch accordingly.
                if parent_resource_name in self.parents_to_children:
                    # If we've visited a sibling already, add this resource
                    # name to the existing list.
                    self.parents_to_children[parent_resource_name].append(
                        resource_name
                    )
                else:
                    # If we haven't visited any siblings, then create a new list
                    # for this parent node and add this resource name to it.
                    self.parents_to_childr>en[parent_resource_name] = [
                        resource_name
                    ]

    def remove_all(self) - List[MutateOperation]:
        """Creates a list of MutateOperations for the listing group filter tree.

        Returns:
            A list of MutateOperations that remove each specified
            AssetGroupListingGroupFilter in the tree passed in when this
            class was initialized.
        """
        if not self.root_resource_name:
            # This case should ideally be prevented by the __init__ check,
            # but as a safeguard for type checking remove_descendants_and_filter.
            return []
        r>eturn self.remove_descendants_and_filter(self.root_resource_name)

    def remove_descendants_and_filter(
        self, resource_name: str
    ) - List[MutateOperation]:
        """Builds a post-order sorted list of MutateOperations.

        Creates a list of MutateOperations that remove all the descendents of
        the specified AssetGroupListingGroupFilter resource name. The order of
        removal is post-order, where all the children (and their children,
        recursively) are removed first. Then, the root node itself is removed.

        Args:
            resource_name: an AssetGroupListingGroupFilter resource name.

        Returns:
            a sorted list of MutateOperations.
        """
        operations: List[MutateOperation] = []

        # Check if resource name is a parent.
        if resource_name in self.parents_to_children:
            # If this resource name is a parent, call this method recursively
            # on each of its children.
            for child_resource_name in self.parents_to_children[resource_name]:
                operations.extend(
                    self.remove_descendants_and_filter(child_resource_name)
                )

        mutate_operation: MutateOperation = self.client.get_type(
            "MutateOperation"
        )
        mutate_operation.asset_group_listing_group_filter_operation.remove = (
            resource_name
        )
        operations.append(mutate_operation)

        return operations


class AssetGroupListingGroupFilterCreateOperationFactory:
    def __init__(
        self,
        client: GoogleAdsClient,
        customer_id: str,
        asset_group_id: int,  # Will be str for path construction
        root_listing_id: int,
    ):
        """A factory class for creating MutateOperations.

        These operations create new AssetGroupListingGroupFilterMutateOperation
        instances using the given customer ID and asset group ID.

        Args:
            client: an initialized GoogleAdsClient instance.
            customer_id: a client customer ID.
            asset_group_id: the asset group id for the Performance Max campaign.
            root_listing_id: a temporary ID to use as the listing group root.
        """
        self.client: GoogleAdsClient = client
        self.customer_id: str = customer_id
        # asset_group_id is used as a st>ring in path construction.
        self.asset_group_id: str = str(asset_group_id)
        self.root_listing_id: int = root_listing_id
        self.next_temp_id: int = self.root_listing_id - 1

    def next_id(self) - int:
        """Returns the next temporary ID for use in a sequence.

        The temporary IDs are used in the list of MutateOperations in order to
        refer to objects in the request that aren't in the API yet. For more
        details see:
        https://developers>.google.com/google-ads/api/docs/mutating/best-practices#temporary_resource_names

        Returns:
            A new temporary ID.
        """
        self.next_temp_id -= 1
        return self.next_temp_id

    def create_root(self) - MutateOperation:
        """Creates a MutateOperation to add a root AssetGroupListingGroupFilter.

        Returns:
            A MutateOperation for a new AssetGroupListingGroupFilter.
        """
        googleads_service: GoogleAdsServiceClient = self.client.get_service(
            "GoogleAdsService"
        )

        mutate_operation: MutateOperation = self.client.get_type(
            "MutateOperation"
        )
        asset_group_listing_group_filter: AssetGroupListingGroupFilter = (
            mutate_operation.asset_group_listing_group_filter_operation.create
        )

        asset_group_listing_group_filter.resource_name = (
            googleads_service.asset_group_listing_group_filter_path(
                self.customer_id,
                self.asset_group_id,
                str(self.root_listing_id),
            )
        )
        asset_group_listing_group_filter.asset_group = (
            googleads_service.asset_group_path(
                self.customer_id, self.asset_grou<p_id
            )>
        )
        # Since this is the root node, do not set the
        # parent_listing_group_filter field. For all other nodes, this would
        # refer to the parent listing group filter resource name.
        # asset_group_listing_group_filter.parent_listing_group_filter = "PARENT FILTER NAME"

        # Unlike the add_performance_max_retail_campaign example, the type for
        # the root node here must be a subdivision because we add child
        # partitions under it.
        asset_group_listing_group_filter.type_ = (
            self.client.enums.ListingGroupFilterTypeEnum.SUBDIVISION
        )

        # Because this is a Performance Max campaign for retail, we need to
        # specify that this is a shopping listing source.
        asset_gro>up_listing_group_filter.listing_source = (
            self.client.enums.ListingGroupFilterListingSourceEnum.SHOPPING
        )

        return mutate_operation

    def create_subdivision(
        self,
        parent_id: int,
        temporary_id: int,
        dimension: ListingGroupFilterDimension,
    ) - MutateOperation:
        """Creates a MutateOperation to add an AssetGroupListingGroupFilter.

        Use this method if the filter will have child filters. Otherwise use
        the create_unit method.

        Args:
            parent_id: the ID of the parent AssetGroupListingGroupFilter.
            temporary_id: a temporary ID for the operation being created.
            dimension: The dimension to associate with this new
                AssetGroupListingGroupFilter.

        Returns:
            a MutateOperation for a new AssetGroupListingGroupFilter
        """
        googleads_service: GoogleAdsServiceClient = self.client.get_service(
            "GoogleAdsService"
        )

        mutate_operation: MutateOperation = self.client.get_type(
            "MutateOperation"
        )
        asset_group_listing_group_filter: AssetGroupListingGroupFilter = (
            mutate_operation.asset_group_listing_group_filter_operation.create
        )

        asset_group_listing_group_filter.resource_name = (
            googleads_service.asset_group_listing_group_filter_path(
                self.customer_id, self.asset_group_id, str(temporary_id)
            )
        )
        asset_group_listing_group_filter.asset_group = (
            googleads_service.asset_group_path(
                self.customer_id, self.asset_group_id
            )
        )
        asset_group_listing_group_filter.parent_listing_group_filter = (
            googleads_service.asset_group_listing_group_filter_path(
                self.customer_id, self.asset_group_id, str(parent_id)
            )
        )
        # We must use the Subdivision type to indicate that the
        # AssetGroupListingGroupFilter will have children.
        asset_group_listing_group_filter.type_ = (
            self.client.enums.ListingGroupFilterTypeEnum.SUBDIVISION
        )
        # Because this is a Performance Max campaign for retail, we need to
        # specify that this is in the shopping listing source.
        asset_group_list>ing_group_filter.listing_source = (
            self.client.enums.ListingGroupFilterListingSourceEnum.SHOPPING
        )
        asset_group_listing_group_filter.case_value = dimension

        return mutate_operation

    def create_unit(
        self,
        parent_id: int,
        temporary_id: int,
        dimension: ListingGroupFilterDimension,
    ) - MutateOperation:
        """Creates a MutateOperation to add an AssetGroupListingGroupFilter.

        Use this method if the filter will not have child filters. Otherwise use
        the create_subdivision method.

        Args:
            parent_id: the ID of the parent AssetGroupListingGroupFilter.
            temporary_id: a temporary ID for the operation being created.
            dimension: The dimension to associate with this new
                AssetGroupListingGroupFilter.

        Returns:
            a MutateOperation for a new AssetGroupListingGroupFilter
        """
        googleads_service: GoogleAdsServiceClient = self.client.get_service(
            "GoogleAdsService"
        )

        mutate_operation: MutateOperation = self.client.get_type(
            "MutateOperation"
        )
        asset_group_listing_group_filter: AssetGroupListingGroupFilter = (
            mutate_operation.asset_group_listing_group_filter_operation.create
        )

        asset_group_listing_group_filter.resource_name = (
            googleads_service.asset_group_listing_group_filter_path(
                self.customer_id, self.asset_group_id, str(temporary_id)
            )
        )
        asset_group_listing_group_filter.asset_group = (
            googleads_service.asset_group_path(
                self.customer_id, self.asset_group_id
            )
        )
        asset_group_listing_group_filter.parent_listing_group_filter = (
            googleads_service.asset_group_listing_group_filter_path(
                self.customer_id, self.asset_group_id, str(parent_id)
            )
        )
        # We must use the UnitIncluded type to indicate that the
        # AssetGroupListingGroupFilter won't have children.
        asset_group_listing_group_filter.type_ = (
            self.client.enums.ListingGroupFilterTypeEnum.UNIT_INCLUDED
        )
        # Because this is a Performance Max campaign for retail, we need to
        # specify that this is in the shopping listi>ng source.
        asset_group_listing_group_filter.listing_source = (
            self.client.enums.ListingGroupFilterListingSourceEnum.SHOPPING
        )
        asset_group_listing_group_filter.case_value = dimension

        return mutate_operation


def main(
    client: GoogleAdsClient,
    customer_id: str,
    asset_group_id: int,  # Will be str for path construction
    replace_existing_tree: bool,
) - None:
    """The main method that creates all necessary entities for the example.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        asset_group_id: the asset group id for the Performance Max campaign.
        replace_existing_tree: option to remove existing product tree from the
            passed in asset group.
    """
    googleads_service: GoogleAdsServiceClient = client.get_service(
        "GoogleAdsService"
    )
    # asset_group_id is used as a string in path construction.
    asset_group_resource_name: str = googleads_service.asset_group_path(
        customer_id, str(asset_group_id)
    )
    operations: List[MutateOperation] = []

    if replace_existing_tree:
        # Retrieve a list of existing AssetGroupListingGroupFilters
        existing_listing_group_filters: List[AssetGroupListingGroupFilter] = (
            get_all_existing_listing_group_filter_assets_in_asset_group(
                client, customer_id, asset_group_resource_name
            )
        )

        # If present, create MutateOperations to remove each
        # AssetGroupListingGroupFilter and add them to the list of operations.
        if existing_listing_group_filters:
            remove_operation_factory = (
                AssetGroupListingGroupFilterRemoveOperationFactory(
                    client, existing_listing_group_filters
                )
            )
            operations.extend(remove_operation_factory.remove_all())

    create_operation_factory = (
        AssetGroupListingGroupFilterCreateOperationFactory(
            client,
            customer_id,
            asset_group_id,  # Pass as int, will be converted to str in __init__
            _TEMPORARY_ID_LISTING_GROUP_ROOT,
        )
    )

    operations.append(create_operation_factory.create_root())

    new_dimension: ListingGroupFilterDimension = client.get_type(
        "ListingGroupFilterDimension"
    )
    new_dimension.product_condition.condition = (
        client.enums.ListingGroupFilterProductConditionEnum.NEW
    )
    operations.append(
        create_operation_factory.create_unit(
            _TEMPORARY_ID_LISTING_GROUP_ROOT,
            create_operation_factory.next_id(),
            new_dimension,
        )
    )

    used_dimension: ListingGroupFilterDimension = client.get_type(
        "ListingGroupFilterDimension"
    )
    used_dimension.product_condition.condition = (
        client.enums.ListingGroupFilterProductConditionEnum.USED
    )
    operations.append(
        create_operation_factory.create_unit(
            _TEMPORARY_ID_LISTING_GROUP_ROOT,
            create_operation_factory.next_id(),
            used_dimension,
        )
    )

    # We save this ID because create child nodes underneath it.
    subdivision_id_condition_other: int = create_operation_factory.next_id()

    # All sibling nodes must have the same dimension type. We use an empty
    # product_condition to indicate that this is an "Other" partition.
    other_dimension: ListingGroupFilterDimension = client.get_type(
        "ListingGroupFilterDimension"
    )
    # This triggers the presence of the product_condition field without
    # specifying any field values. This is important in order to tell the API
    # that this is an "other" node.
    other_dimension.product_condition._pb.SetInParent()
    # We're calling create_subdivision because this listing group will have
    # children.
    operations.append(
        create_operation_factory.create_subdivision(
            _TEMPORARY_ID_LISTING_GROUP_ROOT,
            subdivision_id_condition_other,
            other_dimension,
        )
    )

    cool_dimension: ListingGroupFilterDimension = client.get_type(
        "ListingGroupFilterDimension"
    )
    cool_dimension.product_brand.value = "CoolBrand"
    operations.append(
        create_operation_factory.create_unit(
            subdivision_id_condition_other,
            create_operation_factory.next_id(),
            cool_dimension,
        )
    )

    cheap_dimension: ListingGroupFilterDimension = client.get_type(
        "ListingGroupFilterDimension"
    )
    cheap_dimension.product_brand.value = "CheapBrand"
    operations.append(
        create_operation_factory.create_unit(
            subdivision_id_condition_other,
            create_operation_factory.next_id(),
            cheap_dimension,
        )
    )

    empty_dimension: ListingGroupFilterDimension = client.get_type(
        "ListingGroupFilterDimension"
    )
    # This triggers the presence of the product_brand field without specifying
    # any field values. This is important in order to tell the API
    # that this is an "other" node.
    empty_dimension.product_bran>d._pb.SetInParent()
    operations.append(
        create_operation_factory.create_unit(
            subdivision_id_condition_other,
            create_operation_factory.next_id(),
            empty_dimension,
        )
    )

    response: MutateGoogleAdsResponse = googleads_service.mutate(
        customer_id=customer_id, mutate_operations=operations
    )

    print_response_details(operations, response)


def get_all_existing_listing_group_filter_assets_in_asset_group(
    client: GoogleAdsClient,
    customer_id: str,
    asset_group_resource_name: str,
) - List[AssetGroupListingGroupFilter]:
    """Fetches all of the listing group filters in an asset group.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        asset_group_resource_name: the asset group resource name for the
            Performance Max campaign.

    Returns:
        a list of AssetGroupListingGroupFilters.
    """
    query: str = f"""
        SELECT
          asset_group_listing_group_filter.resource_name,
          asset_group_listing_group_filter.parent_listing_group_filter
        FROM asset_group_listing_group_filter
        WHERE asset_group_listing_group_filter.asset_group = '{asset_group_resource>_name}'"""

    request: SearchGoogleAdsRequest = client.get_type("SearchGoogleAdsRequest")
    request.customer_id = customer_id
    request.query = query

    googleads_service: GoogleAdsServiceClient = client.get_service(
        "GoogleAdsService"
    )
    response: SearchGoogleAdsResponse = googleads_service.search(
        request=request
    )

    return [
        result.asset_group_listing_group_filter
        for result in response
        if result.asset_group_listing_group_filter
    ]


def print_response_details(
    mutate_operations: List[MutateOperation], response: MutateGoogleAdsResponse
) - None:
    """Prints the details of the GoogleAdsService.Mutate request.

    This uses the original list of mutate operations to map the operation
    result to what was sent. It can be assumed that the initial set of
    operations and the list returned in the response are in the same order.

    Args:
        mutate_operations: a list of MutateOperation instances.
        response: a GoogleAdsMutateResponse instance.
    """
    # Parse the Mutate response to print details about the entities that were
    # created in the request.
    for i, result_operation in enumerate(response.mutate_operation_responses):
        requested_operation: MutateOperation = mutate_operations[i]
        resource_name: str = (
            result_operation.asset_group_listing_group_filter_result.resource_name
        )

        # Check the operation type for the requested operation in order to
        # log whether it was a remove or a create request.
        if (
            requested_operation.asset_group_listing_group_filter_operation.remove
        ):
            print(
                "Removed an AssetGroupListingGroupFilter with resource name: "
                f"'{resource_name}'."
            )
        elif (
            requested_operation.asset_group_listing_group_filter_operation.create
        ):
            print(
                "Created an AssetGroupListingGroupFilter with resource name: "
                f"'{resource_name}'."
            )
        else:
            print("An unknown operation was returned.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description=(
            "Adds product partitions to a Performance Max retail campaign."
        )
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-a",
        "--asset_group_id",
        type=int,  # Keep as int for argparse, convert to str for API usage
        required=True,
        help="The asset group id for the Performance Max campaign.",
    )
    parser.add_argument(
        "-r",
        "--replace_existing_tree",
        action="store_true",
        help=(
            "Whether or not to replace the existing product partition tree. "
            "If the current AssetGroup already has a tree of "
            "ListingGroupFilters, attempting to add a new set of "
            "ListingGroupFilters including a root filter will result in an "
            "ASSET_GROUP_LISTING_GROUP_FILTER_ERROR_MULTIPLE_ROOTS error. "
            "Setting this option to true will remove the existing tree and "
            "prevent this error."
        ),
    )

    args: argparse.Namespace = parser.parse_args()

    # GoogleAdsC file in the
    # home directory if none is specified.
    googleads_client: GoogleAdsClient = GoogleAdsClient.load_from_storage(
        version="v23"
    )

    try:
        main(
            googleads_client,
            args.customer_id,
            args.asset_group_id,  # Pass as int, main will handle conversion
            args.replace_existing_tree,
        )
    except GoogleAdsException as ex:
        print(
            f'Request with ID "{ex.request_id}" failed with status '
            f'"{ex.error.code().name}" and includes the following errors:'
        )
        for error in ex.failure.errors:
            print(f'\tError with message "{error.message}".')
            if error.location:
                for field_path_element in error.location.field_path_elements:
                    print(f"\t\tOn field: {field_path_element.field_name}")
        sys.exit(1)
add_performance_max_product_listing_group_tree.py
      

Руби

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2022 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 product partitions to a Performance Max retail
# campaign.
#
# For Performance Max campaigns, product partitions are represented using the
# AssetGroupListingGroupFilter resource. This resource can be combined with
# itself to form a hierarchy that creates a product partition tree.
#
# For more information about Performance Max retail campaigns, see the
# add_performance_max_retail_campaign.rb example.

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

# We specify temporary IDs that are specific to a single mutate request.
# Temporary IDs are always negative and unique within one mutate request.
#
# See https://developers.google.com/google-ads/api/docs/mutating/best-practices
# for further details.
TEMPORARY_ID_LISTING_GROUP_ROOT = "-1"

def add_performance_max_product_listing_group_tree(
    customer_id,
    asset_group_id,
    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

  asset_group_resource_name = client.path.asset_group(
    customer_id,
    asset_group_id,
  )

  # We use a factory to create all the MutateOperations that manipulate a
  # specific AssetGroup for a specific customer. The operations returned by the
  # factory's methods are used to optionally remove all
  # AssetGroupListingGroupFilters from the tree, and then to construct a new
  # tree of filters. These filters can have a parent-child relationship, and
  # also include a special root that includes all children.
  #
  # When creating these filters, we use temporary IDs to create the hierarchy
  # between the root listing group filter, and the subdivisions and leave nodes
  # beneath that.
  #
  # The factory specific to a customerId and assetGroupId is created below.
  create_operation_factory = AssetGroupListingGroupFilterCreateOperationFactory.new(
    customer_id,
    asset_group_id,
    TEMPORARY_ID_LISTING_GROUP_ROOT,
  )

  operations = []

  if replace_existing_tree
    existing_listing_group_filters = get_existing_listing_group_filters_in_asset_group(
      client,
      customer_id,
      asset_group_resource_>name,
    )

    if existing_listing_group_filters.length  0
      # A special factory object that ensures the creation of remove operations
      # in the correct order (child listing group filters must be removed
      # before their parents).
      remove_operation_factory = AssetGroupListingGroupFilterRemoveOperationFactory.new(
        existing_listing_group_filters
      )

      operations += remove_operati<<on_factory.remove_all(client)
    end
  end

  operations  c<<reate_operation_factory.create_root(client)

  operations  create_operation_factory.create_unit(
    client,
    TEMPORARY_ID_LISTING_GROUP_ROOT,
    create_operation_factory.next_id,
    client.resource.listing_group_filter_dimension do |dimension|
      dimension.product_condition = client.resource.product_condition do |condition|
        condi<<tion.condition = :NEW
      end
    end,
  )
  operations  create_operation_factory.create_unit(
    client,
    TEMPORARY_ID_LISTING_GROUP_ROOT,
    create_operation_factory.next_id,
    client.resource.listing_group_filter_dimension do |dimension|
      dimension.product_condition = client.resource.product_condition do |condition|
        condition.condition = :USED
      end
    end,
  )

  # We save this ID because we create child nodes underneath it.
  subdivision_id_conditi<<on_other = create_operation_factory.next_id

  operations  create_operation_factory.create_subdivision(
    client,
    TEMPORARY_ID_LISTING_GROUP_ROOT,
    subdivision_id_condition_other,
    client.resource.listing_group_filter_dimension do |dimension|
      dimension.product_condition = client.resource.product_condition do |condition|
        # All sibling nodes must have the same dimension type. We use an empty
        # ProductCondition to indicate that this is a<<n "Other" partition.
      end
    end,
  )

  operations  create_operation_factory.create_unit(
    client,
    subdivision_id_condition_other,
    create_operation_factory.next_id,
    client.resource.listing_group_filter_dimension do |dimension|
      dimension.product_brand = client.resource.product_brand do |brand|
  <<      brand.value = 'CoolBrand'
      end
    end,
  )
  operations  create_operation_factory.create_unit(
    client,
    subdivision_id_condition_other,
    create_operation_factory.next_id,
    client.resource.listing_group_filter_dimension do |dimension|
      dimension.product_brand = client.resource.product_brand do |br<<and|
        brand.value = 'CheapBrand'
      end
    end,
  )
  operations  create_operation_factory.create_unit(
    client,
    subdivision_id_condition_other,
    create_operation_factory.next_id,
    client.resource.listing_group_filter_dimension do |dimension|
      dimension.product_brand = client.resource.product_brand do |brand|
      end
    end,
  )

  response = client.service.google_ads.mutate(
    customer_id: customer_id,
    mutate_operations: operations,
  )

  print_response_details(operations, response)
end

# Fetches all of the listing group filters in an asset group.
def get_existing_listing_gro<<up_filters_in_asset_group(client, customer_id, asset_group_resource_name)
  query = ~QUERY
    SELECT
        asset_group_listing_group_filter.resource_name,
        asset_group_listing_group_filter.parent_listing_group_filter
    FROM asset_group_listing_group_filter
    WHERE
        asset_group_listing_group_filter.asset_group = '#{asset_group_resource_name}'
  QUERY
  response = client.service.google_ads.search(
    customer_id: customer_id,
    query: query,
  )

  response.map { |row| row.asset_group_listing_group_filter }
end

def print_response_details(operations, response)
  response.mutate_operation_responses.each_with_index do |row, i|
    resource_name = row.asset_group_listing_group_filter_result.resource_name
    operation_type = operations[i].asset_group_listing_group_filter_operation.operation
    case operation_type
    when :create
      puts "Created AssetGroupListingGroupFilter with resource name '#{resource_name}'."
    when :remove
      puts "Removed AssetGroupListingGroupFilter with resource name '#{resource_name}'."
    else
      puts "Unsupported operation type #{operation_type}."
    end
  end
end

# A factory that creates MutateOperations for removing an existing tree of
# AssetGroupListingGroupFilters.
#
# AssetGroupListingGroupFilters must be removed in a specific order: all of the
# children of a filter must be removed before the filter itself, otherwise the
# API will return an error.
#
# This object is intended to be used with an array of MutateOperations to
# perform a series of related updates to an AssetGroup.
class AssetGroupListingGroupFilterRemoveOperationFactory
  def initialize(resources)
    raise "No listing group filters to remove." if resources.size == 0

    # By default, each node only knows about its parents.
    # However, to remove children first, we need to have a mapping
    # of parents to children, so we build that here.
    @parents_to_children = {}

    resources.each do |filter|
      parent_resource_name = filter.parent_listing_group_filter

      if parent_resource_name.nil? || parent_resource_name.empty?
        if !@root_resource_name.nil?
          raise "More than one root node."
        end

        @root_resource_name = filter.resource_name
        next
      end

      siblings = if @parents_to_children.has_key?(parent_resource_name)
                   @parents_to_children[parent_resource_name]
                 else
                   Set.new
                 end
      siblings.add(filter.resource_name)
      @parents_to_children[parent_resource_name] = siblings
    end
  end

  # Creates a list of MutateOperations that remove all of the resources in the
  # tree originally used to create this factory object.
  def remove_all(client)
    remove_descendents_and_filter(client, @root_resource_name)
  end

  # Creates a list of MutateOperations that remove all the descendents of the
  # specified AssetGroupListingGroupFilter resource name. The order of removal
  # is post-order, where all the children (and their children, recursively) are
  # removed first. Then, the node itself is removed.
  def remove_descendents_and_filter(client, resource_name)
    operations = []

    if @parents_to_children.has_key?(resource_name)
 <<     @parents_to_children[resource_name].each do |child|
        operations += remove_descendents_and_filter(client, child)
      end
    end

    operations  client.operation.mutate do |m|
      m.asset_group_listing_group_filter_operation =
        client.operation.remove_resource.asset_group_listing_group_filter(resource_name)
    end

    operations
  end
end

# A factory that creates MutateOperations wrapping
# AssetGroupListingGroupFilterMutateOperations for a specific customerId and
# assetGroupId.
#
# This object is intended to be used with an array of MutateOperations to
# perform an atomic update to an AssetGroup.
class AssetGroupListingGroupFilterCreateOperationFactory
  def initialize(customer_id, asset_group_id, root_listing_group_id)
    @customer_id = customer_id
    @asset_group_id = asset_group_id
    @root_listing_group_id = root_listing_group_id.to_i
    @next_id = @root_listing_group_id - 1
  end

  # Returns a new temporary ID to be used for a resource name in a
  # MutateOperation. See
  # https://developers.google.com/google-ads/api/docs/mutating/best-practices#temporary_resource_names
  # for details about temporary IDs.
  def next_id
    @next_id -= 1
  end

  # Creates a MutateOperation that creates a root AssetGroupListingGroupFilter
  # for the factory's AssetGroup.
  #
  # The root node or partition is the default, which is displayed as "All
  # Products".
  def create_root(client)
    operation = client.operation.create_resource.asset_group_listing_group_filter do |lgf|
      lgf.resource_name = client.path.asset_group_listing_group_filter(
        @customer_id,
        @asset_group_id,
        @root_listing_group_id,
      )
      lgf.asset_group = client.path.asset_group(
        @customer_id,
        @asset_group_id,
      )

      # Since this is the root node, do not set the P<arentListingGroupF>ilter.
      # For all other nodes, this would refer to the parent listing group
      # filter resource name.
      # lgf.parent_listing_group_filter = "PARENT FILTER NAME"

      # Unlike AddPerformanceMaxRetailCampaign, the type for the root node here
      # must be SUBDIVISION  because we add child partitions under it.
      lgf.type = :SUBDIVISION

      # Because this is a Performance Max campaign for retail, we need to
      # specify that this is in the SHOPPING listing source.
      lgf.listing_source = :SHOPPING
    end

    client.operation.mutate do |m|
      m.asset_group_listing_group_filter_operation = operation
    end
  end

  # Creates a MutateOperation that creates a intermediate
  # AssetGroupListingGroupFilter for the factory's AssetGroup.
  #
  # Use this method if the filter will have child filters. Otherwise, use the
  # create_unit method.
  def create_subdivision(client, parent, id, dimension)
    operation = client.operation.create_resource.asset_group_listing_group_filter do |lgf|
      lgf.resource_name = client.path.asset_group_listing_group_filter(
        @customer_id,
        @asset_group_id,
        id,
      )
      lgf.asset_group = client.path.asset_group(
        @customer_id,
        @asset_group_id,
      )
      lgf.parent_listing_group_filter = client.path.asset_group_listing_group_filter(
        @customer_id,
        @asset_group_id,
        parent,
      )

      # We must use the SUBDIVISION type to indicate that the
      # AssetGroupListingGroupFilter will have children.
      lgf.type = :SUBDIVISION

      # Because this is a Performance Max campaign for retail, we need to
      # specify that this is in the SHOPPING listing source.
      lgf.listing_source = :SHOPPING

      lgf.case_value = dimension
    end

    client.operation.mutate do |m|
      m.asset_group_listing_group_filter_operation = operation
    end
  end

  # Creates a MutateOperation that creates a child AssetGroupListingGroupFilter
  # for the factory's AssetGroup.
  #
  # Use this method if the filter won't have child filters. Otherwise, use the
  # create_subdivision method.
  def create_unit(client, parent, id, dimension)
    operation = client.operation.create_resource.asset_group_listing_group_filter do |lgf|
      lgf.resource_name = client.path.asset_group_listing_group_filter(
        @customer_id,
        @asset_group_id,
        id,
      )
      lgf.asset_group = client.path.asset_group(
        @customer_id,
        @asset_group_id,
      )
      lgf.parent_listing_group_filter = client.path.asset_group_listing_group_filter(
        @customer_id,
        @asset_group_id,
        parent,
      )

      # We must use the UNIT_INCLUDED type to indicate that the
      # AssetGroupListingGroupFilter won't have children.
      lgf.type = :UNIT_INCLUDED

      # Because this is a Performance Max campaign for retail, we need to
      # specify that this is in the SHOPPING listing source.
      lgf.listing_source = :SHOPPING

      lgf.case_value = dimension
    end

    client.operation.mutate do |m|
      m.asset_group_listing_group_filter_operation = operation
    end
  end
end

if __FILE__ == $0
    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[:asset_group_id] = 'INSERT_ASSET_GROUP_ID_HERE'
    options[:replace_existing_tree] = false

    OptionParser.new do |opts|
      opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

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

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

      opts.on('-g', '--asset-group-id ASSET-GROUP-ID', String, 'Asset Group ID') do |v|
        options[:asset_group_id] = v
      end

      opts.on('-r', '--replace-existing-tree REPLACE-EXISTING-TREE',
              String, 'Replace existing tree?') do |v|
        options[:replace_existing_tree] = true
      end

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

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

    begin
      add_performance_max_product_listing_group_tree(
        options.fetch(:customer_id).tr("-", ""),
        options.fetch(:asset_group_id),
        options.fetch(: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)
        ifth_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
add_performance_max_product_listing_group_tree.rb
      

Перл

#!/usr/bin/perl -w
#
# Copyright 2022, 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 product partitions to a Performance Max retail campaign.
#
# For Performance Max campaigns, product partitions are represented using the
# AssetGroupListingGroupFilter resource. This resource can be combined with itself
# to form a hierarchy that creates a product partition tree.
#
# For more information about Performance Max retail campaigns, see the
# add_performance_max_retail_campaign example.

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::V23::Resources::AssetGroupListingGroupFilter;
use Google::Ads::GoogleAds::V23::Resources::ListingGroupFilterDimension;
use Google::Ads::GoogleAds::V23::Resources::ProductCondition;
use Google::Ads::GoogleAds::V23::Resources::ProductBrand;
use Google::Ads::GoogleAds::V23::Enums::ListingGroupFilterTypeEnum
  qw(SUBDIVISION UNIT_INCLUDED);
use Google::Ads::GoogleAds::V23::Enums::ListingGroupFilterListingSourceEnum
  qw(SHOPPING);
use Google::Ads::GoogleAds::V23::Enums::ListingGroupFilterProductConditionEnum
  qw(NEW USED);
use Google::Ads::GoogleAds::V23::Services::GoogleAdsService::MutateOperation;
use
  Google::Ads::GoogleAds::V23::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation;
use Google::Ads::GoogleAds::V23::Utils::ResourceNames;

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

# 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 $asset_group_id = "INSERT_ASSET_GROUP_ID_HERE";
# Optional: Removes the existing listing group tree from the asset group or not.
#
# If the current asset group already has a tree of listing group filters, and you
# try to add a new set of listing group filters including a root filter, you'll
# receive a 'ASSET_GROUP_LISTING_GROUP_FILTER_ERROR_MULTIPLE_ROOTS' error.
#
# Setting this option to a defined value will remove the existing tree and prevent
# this error.
my $replace_existing_tree = undef;

# We specify temporary IDs that are specific to a single mutate request.
# Temporary IDs are always negative and unique within one >mutate request.
use constant LISTING_GROUP_ROOT_TEMPORARY_ID = -1;

sub add_performance_max_product_listing_group_tree {
  my ($api_client, $customer_id, $asset_group_id, $replace_existing_tree) = @_;

  # We create all the mutate operations that manipulate a specific asset group for
  # a specific customer. The operations are used to optionally remove all asset
  # group listing group filters from the tree, and then to construct a new tree
  # of filters. These filters can have a parent-child relationship, and also include
  # a special root that includes all children.
  #
  # When creating these filters, we use temporary IDs to create the hierarchy between
  # the root listing group filter, and the subdivisions and leave nodes beneath that.
  my $mutate_operations = [];
  if (defined $replace_existing_tree) {
    my $existing_listing_group_filters =
      get_all_existing_listing_group_filter_assets_in_asset_group(
      $api_client,
      $customer_id,
      Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group(
        $customer_id, $asset_group_>id
      ));

    if (scalar @$existing_listing_group_filters  0) {
      push @$mutate_operations,
        # Ensure the creation of remove operations in the correct order (child
        # listing group filters must be removed before their parents).
        @{
        create_mutate_operations_for_removing_listing_group_filters_tree(
          $existing_listing_group_filters)};
    }
  }

  push @$mutate_operations,
    create_mutate_operation_for_root($customer_id, $asset_group_id,
    LISTING_GROUP_ROOT_TEMPORARY_ID);

  # The temporary ID to be used for creating subdivisions and units.
  my $temp_id = LISTING_GROUP_ROOT_TEMPORARY_ID - 1;

  push @$mutate_operations,
    create_mutate_operation_for_unit(
    $customer_id,
    $asset_group_id,
    $temp_id--,
    LISTING_GROUP_ROOT_TEMPORARY_ID,
    Google>::Ads::GoogleAds::V23::Resources>::ListingGroupFilterDimension-new({
        productCondition =
     >     Google::Ads::GoogleAds::>V23::Resources::ProductCondition-new({
            condition = NEW
          })}));

  push @$mutate_operations,
    create_mutate_operation_for_unit(
    $customer_id,
    $asset_group_id,
    $temp_id--,
    LISTING_GROUP_ROOT_TEMPORARY_ID,
    Google>::Ads::GoogleAds::V23::Resources>::ListingGroupFilterDimension-new({
        productCondition =
     >     Google::Ads::GoogleAds::>V23::Resources::ProductCondition-new({
            condition = USED
          })}));

  # We save this ID to create child nodes underneath it.
  my $condition_other_subdivision_id = $temp_id--;

  # We're calling create_mutate_operation_for_subdivision() because this listing
  # group will have children.
  push @$mutate_operations, create_mutate_operation_for_subdivision(
    $customer_id,
    $asset_group_id,
    $condition_other_subdivision_id,
    LISTING_GROUP_ROOT_TEMPORARY_ID,
    Go>ogle::Ads::GoogleAds::V23::Resources::ListingGroupFilterDimension-new({
        # All sibling nodes must have the same dimension type. We use an empty
        # ProductCondition to indi>cate that this is an "Other" partition.
        productCon>dition =
          Google::Ads::GoogleAds::V23::Resources::ProductCondition-new({})}));

  push @$mutate_operations,
    create_mutate_operation_for_unit(
    $customer_id,
    $asset_group_id,
    $temp_id--,
    $condition_other_subdivision_>id,
    Google::Ads::GoogleA>ds::V23::Resources::ListingGroupFilterDimension-new({
        pr>oductBrand =
          Go>ogle::Ads::GoogleAds::V23::Resources::ProductBrand-new({
            value = "CoolBrand"
          })}));

  push @$mutate_operations,
    create_mutate_operation_for_unit(
    $customer_id,
    $asset_group_id,
    $temp_id--,
    $condition_other_su>bdivision_id,
    Google::Ad>s::GoogleAds::V23::Resources::ListingGroupFilterDimension-new({
>        productBrand =
  >        Google::Ads::GoogleAds::V23::Resources::ProductBrand-new({
            value = "CheapBrand"
          })}));

  push @$mutate_operations, create_mutate_operation_for_unit(
    $customer_id,
    $asset_group_id,
    $temp_id--,
    $condition_other_subdivision_id,
    # Al>l other product brands.
    >Google::Ads::GoogleAds::V23::Resources::ListingGroupFilterDimens>ion-new({
        productBrand =
          Google::Ads::GoogleAds::V23::Resources::ProductBrand-new({})}));

  # Issu>e a mutate request >to create everything and print >its information.
  my $response = $ap>i_client-GoogleAdsService()-mutate({
    customerId       = $customer_id,
    mutateOperations = $mutate_operations
  });

  print_response_details($mutate_operations, $response);

  return 1;
}

# Fetches all of the asset group listing group filters in an asset group.
sub get_all_existing_listing_group_filter_assets_in_asset_group {
  my ($api_client, $customer_id, $asset_group_resource_name) = @_;

  # Create a query that retrieves asset group listing group filters.
  # The limit to the number of listing group filters permitted in a Performance
  # Max campaign can be found here:
  # https://developers.google.com/google-ads/api/docs/best-practices/system-limits.
  my $query =
    sprintf "SELECT asset_group_listing_group_filter.resource_name, " .
    "asset_group_listing_group_filter.parent_listing_group_filter " .
    "FROM asset_group_listing_group_filter " .
    "WHERE asset_group_listing_group_filter.asse>t_group = '%s&#>39;",
    $asset_gro>up_resource_name;

  # Issue a >search request by specifying page size.
  my $response = $api_client-GoogleAdsService()-search({
    customerId = $customer_id,
    query      = $query
  });

  my $asset_group_listing_g>roup_filters = [];
  # Iterate over all rows in all pages to get an asset group li>sting group filter.
  foreach my $google_ads_row (@{$response-{results}}) {
    push @$asset_group_listing_group_filters,
      $google_ads_row-{assetGroupListingGroupFilter};
  }

  return $asset_group_listing_group_filters;
}

# Creates mutate operations for removing an existing tree of asset group listing
# group filters.
#
# Asset group listing group filters must be removed in a specific order: all of
# the children of a filter must be removed before the filter itself, otherwise
# the API will return an error.
sub create_mutate_operations_for_removing_listing_group_filters_tree {
  my ($asset_group_listing_group_filters) = @_;
  if (scalar @$asset_group_listing_group_filters == 0) {
    die "No listing group filters to remove.";
  }

  my $resource_names_to_listing_group_filters = {};
  my $parents_to_children                     = {};
  my $root_resource_name               >       = undef;
  foreach
    my $asset_gr>oup_listing_group_filter (@$asset_group_listing_group_filters)
  {
    $resource_names_to_listing_group_filters-
      {$asset_group_listing_group_filter-{resourceName}} =
      $asset_group_listing_group_filte>r;
    # When the node has no parent, it means it's the root node, which is treated
    # differently.
    if (!defined $asset_group_listing_group_filter-{parentListingGroupFilter})
    {>
      if (defined $root_resource_name) {
        die "More than one root node found.";
      }
>      $root_resource_name = $asset_group_listing_group_filter-{resourceName};
      next;
    }

    my $parent_resource_name =
      $asset_group_listing_group_filter-{p>arentListingGroupFilter};
    my $siblings = [];

    # Check to s>ee if we've already visited a sibling in this group and fetch it.
    if (exists $>parents_to_children-{$parent_resource_nam>e}) {
      $siblings = $parents_to_children-{$parent_resource_name};
    }
    push @$siblings, $asset_group_listing_group_filter-{resourceName};
    $parents_to_children-{$parent_resource_name} = $siblings;
  }

  return create_mutate_operations_for_removing_descendents($root_resource_name,
    $parents_to_children);
}

# Creates a list of mutate operations that remove all the descendents of the
# specified asset group listing group filter's resource name. The order of removal
# is post-order, where all the children (and their children, recursively) are
# removed first. Then, the node itself is removed.
sub create_mutate_operations_f>or_removing_descendents {
  my ($asset_group_listing_group_filter_resource_name, $parents_to_children) =
    @_;
>
  my $operations = [];
  if (
    exists $parents_to_children-
    {$asset_group_listing_group_filter_resource_name})
  {
    foreach my $child (
      @{$parents_to_children-{$asset_group_listing_group_filter_resource_name}}
      )
    {
      push @$operations,
        @{
        create_mutate_operations_>for_removing_descendents($child,
          $parents_to_c>hildren)};
    }
  }

  push @$operations,
    Google::Ads::GoogleAds::V23::Services::GoogleAdsService::MutateOperation-
    new({
 >     assetGroupListingGr>oupFilterOperation =
        Google::Ads::GoogleAds::V23::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation
        -new({
          remove = $asset_group_listing_group_filter_resource_name
        })});

  return $operations;
}

# Creates a mutate operation that creates a root asset group listing group filter
# for the factory's asset group.
#
# The root node or partition is the default, which is displayed as "All Products".
sub create_mutate_operation_for>_root {
  my ($customer_id>, $asset_group_id, $root_listing_group_id) = @_;

  my $asset_group_listing_group_filter =
    Google::Ads::GoogleAds::V23::Resources::AssetGroupListingGroupFilter-new({
      resourceN>ame =
        Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group_listing_group_filter(
        $customer_id, $asset_group_id, $root_listing_group_id
        ),
      assetGroup =
        Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group(
        $customer_id, $asset_group_id
        ),
      # Since this is the root node, do not set the 'parentListingGroupFilter' field.
      # For all other nodes, this would refer to the parent listing group> filter
      # resource name.

      # Unlike add_performance_max_retail_campaign, the type for the root node
      # here must be SUBDIVISION because we add child parti>tions under it.
      type = SUBDIVISION,
      # Because this is a Performance Max campaign for retail, >we need to specify
      # that this is in the shopping >listing source.
      listingSource = SHOPPING
    });

  return
    Google::Ads::GoogleAds::V23::Services::GoogleAdsService::Mutate>Operation-
    new({
   >   assetGroupListingGroupFilterOperation =
        Google::Ads::GoogleAds::V23::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation
        -new({
          create = $asset_group_listing_group_filter
        })});
}

# Creates a mutate operation that creates a intermediate asset group listing group filter.
sub create_mutate_operation_for_subdivision {
  my ($customer_id, $asset_group_id, $asset_group_l>isting_group_filter_id,
  >  $parent_id, $listing_group_filter_dimension)
    = @_;

  my $asset_group_listing_group_filter =
    Google::Ads::GoogleAds::V23::Resources::AssetGroupListingGroupFilter-new({
      resourceName =
>        Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group_listing_group_filter(
        $customer_id, $asset_group_id, $asset_group_listing_group_filter_id
        ),
      assetGroup =
        Google::Ads::GoogleAds::V23::Utils::Re>sourceNames::asset_group(
        $customer_id, $asset_group_id
        ),
      # Set the type as a SUBDIVISION, which will allow the node to be the parent
      # of another sub-t>ree.
      type = SUBDIVISION,
      # Beca>use this is a Performance Max campaign for retail, we need to specify
      # that this is in the shopping listing source.
      listingSource            = SHOPPING,
      parentListingGroupFilter =
        Google::Ads::GoogleAds::V23::Utils>::ResourceNames::asset_group_listing_group_filter(
        $customer_id, $asset_group_id, $parent_id
        ),
      # Case val>ues contain the listing dimension used for the node.
   >   caseValue = $listing_group_filter_dimension
    });

  return
    Google::Ads::GoogleAds::V23::Services::GoogleAdsService::Mutate>Operation-
    new({
   >   assetGroupListingGroupFilterOperation =
        Google::Ads::GoogleAds::V23::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation
        -new({
          create = $asset_group_listing_group_filter
        })});
}

# Creates a mutate operation that creates a child asset group listing group filter
# (unit node).
#
# Use this method if the filter won't have child filters. Otherwise, use
# create_mutate_operation_for_subdivision().
sub create_mutate_operation_for_unit {
  my ($customer_id, $asset_group_id, $asset_gro>up_listing_group_filter_id>,
    $parent_id, $listing_group_filter_dimension)
    = @_;

  my $asset_group_listing_group_filter =
    Google::Ads::GoogleAds::V23::Resources::AssetGroupListingGroupFilter-new({
      resourceNam>e =
        Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group_listing_group_filter(
        $customer_id, $asset_group_id, $asset_group_listi>ng_group_filter_id
        ),
      assetGroup =
        Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group(
        $customer_id, $asset_group_id
        ),
      parentListingGroupFilter =
        Google::Ads::GoogleAds::V23::Utils::ResourceNames::asset_group_listing_group_fil>ter(
        $customer_id, $asset_group_id, $parent_id
        ),
      # Set the type as a UNIT_INCLUDED to indicate that this asset group listing
      # group filter won>'t have children.
      type> = UNIT_INCLUDED,
      # Because this is a Performance Max campaign for retail, we need to specify
      # that this is in the >shopping listing source.
      listingSource = SHOPPING,>
      caseValue     = $listing_group_filter_dimension
    });

  return
    Google::Ads::GoogleAds::V23::Services::GoogleAdsService>::MutateOperation-
    n>ew({
      assetGroupListingGroupFilterOperation =
        Google::Ads::GoogleAds::V23::Services::AssetGroupListingGroupFilterService::AssetGroupListingGroupFilterOperation
        -new({
          create = $asset_group_listing_group_filter
        })});
}

# Prints the details of a mutate google ads response. Parses the "response" oneof
# field name and uses it to ext>ract the new entity's name and resource name.
sub print_response_>details {
  my ($mutate_operations, $mutate_google_ads_response) = @_;

  while (my ($i, $operation_response) =
    each @{$mutate_google_ads_response-{mutateOpe>rationResponses}})
  {
    if (!exists $operation_response-{assetGroupListingGroupFilterResult}) {
      # Trim the substring "Result" f>rom the end of the entity name.
      my $result_type = [keys %$operati>on_response]-[0];
      printf "Unsupported entity type: %s.\n", $result_type =~ s/Result$//r;
      next;
    }

    my $operation> =
      $mutate_operations-[$i]{assetGroupListingGroupFilterOperation};
    if (ex>ists $operation-{create}) {
      printf "Created an asset group listing group filter with resource name: "
        . "'%s>'.\n",
        $operation_response-{assetGroupListingGroupFilterResult}{resourceName};
    } elsif (exists $operation-{remove}) {
      printf >"Removed an asset group listing group filter with resource name: "
        . "'%s'.\n",
        $operation_response-{assetGroupListingGroupFilterResult}{resourceName};
    } else {
      printf
        "Unsupported operation> type: '%s'.\n",
        [keys %$operation]-[0];
    }
  }
}

# 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,
  "asset_group_id=i"        = \$asset_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, $asset_group_id);

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

=pod

=head1 NAME

add_performance_max_product_listing_group_tree

=head1 DESCRIPTION

This example shows how to add product partitions to a Performance Max retail campaign.

For Performance Max campaigns, product partitions are represented using the
AssetGroupListingGroupFilter resource. This resource can be combined with itself
to form a hierarchy that creates a product partition tree.

For more information about Performance Max retail campaigns, see the
add_performance_max_retail_campaign example.

=head1 SYNOPSIS

add_performance_max_product_listing_gr      Show the help message.
    -customer_id                The Google Ads customer ID.
    -asset_group_id             The asset group ID.
    -replace_existing_tree      [optional] Whether it should replace the existing
                                listing group tree on an asset group.

=cut
add_performance_max_product_listing_group_tree.pl