Java
// Copyright 2020 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. package com.google.ads.googleads.examples.extensions; import static com.google.ads.googleads.examples.utils.CodeSampleHelper.getPrintableDateTime; 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.v15.common.MatchingFunction; import com.google.ads.googleads.v15.enums.AffiliateLocationFeedRelationshipTypeEnum.AffiliateLocationFeedRelationshipType; import com.google.ads.googleads.v15.enums.AffiliateLocationPlaceholderFieldEnum.AffiliateLocationPlaceholderField; import com.google.ads.googleads.v15.enums.FeedOriginEnum.FeedOrigin; import com.google.ads.googleads.v15.enums.PlaceholderTypeEnum.PlaceholderType; import com.google.ads.googleads.v15.errors.GoogleAdsError; import com.google.ads.googleads.v15.errors.GoogleAdsException; import com.google.ads.googleads.v15.resources.AttributeFieldMapping; import com.google.ads.googleads.v15.resources.CampaignFeed; import com.google.ads.googleads.v15.resources.CustomerFeed; import com.google.ads.googleads.v15.resources.Feed; import com.google.ads.googleads.v15.resources.Feed.AffiliateLocationFeedData; import com.google.ads.googleads.v15.resources.FeedMapping; import com.google.ads.googleads.v15.services.CampaignFeedOperation; import com.google.ads.googleads.v15.services.CampaignFeedServiceClient; import com.google.ads.googleads.v15.resources.FeedName; import com.google.ads.googleads.v15.services.FeedOperation; import com.google.ads.googleads.v15.services.FeedServiceClient; import com.google.ads.googleads.v15.services.GoogleAdsRow; import com.google.ads.googleads.v15.services.GoogleAdsServiceClient; import com.google.ads.googleads.v15.services.GoogleAdsServiceClient.SearchPagedResponse; import com.google.ads.googleads.v15.services.MutateCampaignFeedsResponse; import com.google.ads.googleads.v15.services.MutateFeedsResponse; import com.google.ads.googleads.v15.utils.ResourceNames; import com.google.api.gax.grpc.GrpcStatusCode; import com.google.api.gax.rpc.ApiException; import com.google.common.collect.ImmutableList; import io.grpc.Status.Code; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; /** * Adds a feed that syncs retail addresses for a given retail chain ID and associates the feed with * a campaign for serving affiliate location extensions. */ public class AddAffiliateLocationExtensions { private static final int MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10; private static class AddAffiliateLocationExtensionsParams extends CodeSampleParams { @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true) private long customerId; @Parameter(names = ArgumentNames.CAMPAIGN_ID, required = true) private long campaignId; @Parameter( names = ArgumentNames.CHAIN_ID, description = "The retail chain ID. See" + " https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids" + " a complete list of valid retail chain IDs", required = true) private long chainId; } public static void main(String[] args) throws IOException { AddAffiliateLocationExtensionsParams params = new AddAffiliateLocationExtensionsParams(); 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.campaignId = Long.parseLong("INSERT_CAMPAIGN_ID_HERE"); params.chainId = Long.parseLong("INSERT_CHAIN_ID_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 AddAffiliateLocationExtensions() .runExample(googleAdsClient, params.customerId, params.campaignId, params.chainId); } 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. gae.printStackTrace(); System.err.printf( "Request ID %s failed due to GoogleAdsException. Underlying errors:%n", gae.getRequestId()); int i = 0; for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) { System.err.printf(" Error %d: %s%n", i++, googleAdsError); } } } /** Runs the example. */ private void runExample( GoogleAdsClient googleAdsClient, long customerId, long campaignId, long chainId) { String feedResourceName = createAffiliateLocationExtensionFeed(googleAdsClient, customerId, chainId); // The feed created above will not be available for use in a campaign feed until the associated // feed mappings have been created by Google. FeedMapping feedMapping = waitForFeedToBeReady(googleAdsClient, customerId, feedResourceName); // Creates the campaign feed now that the feed mappings are ready. createCampaignFeed( googleAdsClient, customerId, campaignId, feedMapping, feedResourceName, chainId); } /** Creates an affiliate location extension feed. */ private String createAffiliateLocationExtensionFeed( GoogleAdsClient googleAdsClient, long customerId, long chainId) { // Removes all existing location extension feeds. This is an optional step, but is required for // this code example to run correctly more than once. This is because: // 1. Google Ads only allows one location extension feed per email address. // 2. A Google Ads account cannot have a location extension feed and an affiliate // location extension feed at the same time. removeLocationExtensionFeeds(googleAdsClient, customerId); // Creates a feed that will sync to retail addresses for a given retail chain ID. // Do not add FeedAttributes to this object as Google Ads will add // them automatically because this will be a system generated feed. Feed feed = Feed.newBuilder() .setName("Affiliate Location Extension feed #" + getPrintableDateTime()) .setAffiliateLocationFeedData( AffiliateLocationFeedData.newBuilder() .addChainIds(chainId) .setRelationshipType(AffiliateLocationFeedRelationshipType.GENERAL_RETAILER)) // Since this feed's contents will be managed by Google, // you must set its origin to GOOGLE. .setOrigin(FeedOrigin.GOOGLE) .build(); // Constructs an operation to create the feed. FeedOperation op = FeedOperation.newBuilder().setCreate(feed).build(); // Creates the FeedServiceClient. try (FeedServiceClient feedService = googleAdsClient.getLatestVersion().createFeedServiceClient()) { // Issues a mutate request to add the feed. MutateFeedsResponse response = feedService.mutateFeeds(String.valueOf(customerId), ImmutableList.of(op)); // Displays the results. String resourceName = response.getResults(0).getResourceName(); System.out.printf( "Affliate location extension feed created with resource name: %s.%n", resourceName); return resourceName; } } /** Removes all location extension feeds. */ private void removeLocationExtensionFeeds(GoogleAdsClient googleAdsClient, long customerId) { // Removes a location extension feed. Does this with the following steps: // 1. Retrieves the existing CustomerFeed links. // 2. Removes the CustomerFeed so that the location extensions from the feed stop serving. // 3. Removes the feed so that Google Ads will no longer sync from the Business Profile // account. List<CustomerFeed> oldCustomerFeeds = getLocationExtensionCustomerFeeds(googleAdsClient, customerId); if (oldCustomerFeeds.isEmpty()) { // Optional: You may also want to remove the CampaignFeeds and AdGroupFeeds. removeCustomerFeeds(googleAdsClient, customerId, oldCustomerFeeds); } List<Feed> feeds = getLocationExtensionFeeds(googleAdsClient, customerId); if (!feeds.isEmpty()) { removeFeeds(googleAdsClient, customerId, feeds); } } /** Creates a CampaignFeed. This links a preexisting {@link Feed} to a campaign. */ private void createCampaignFeed( GoogleAdsClient googleAdsClient, long customerId, long campaignId, FeedMapping feedMapping, String feedResourceName, long chainId) { // Gets the attribute ID that is used for the chain ID. This will be used to filter the feed to // extract affiliate locations. long attributeIdForChainId = getAttributeIdForChainId(feedMapping); // Extracts the feed ID. long feedId = Long.valueOf(FeedName.parse(feedResourceName).getFeedId()); // Constructs a matching function string which filters the Feed to extract affiliate locations. String matchingFunction = String.format("IN(FeedAttribute[%d, %d], %d)", feedId, attributeIdForChainId, chainId); // Creates a CampaignFeed object. CampaignFeed campaignFeed = CampaignFeed.newBuilder() .setFeed(feedResourceName) .addPlaceholderTypes(PlaceholderType.AFFILIATE_LOCATION) .setMatchingFunction( MatchingFunction.newBuilder().setFunctionString(matchingFunction).build()) .setCampaign(ResourceNames.campaign(customerId, campaignId)) .build(); // Creates an operation to create the CampaignFeed. CampaignFeedOperation operation = CampaignFeedOperation.newBuilder().setCreate(campaignFeed).build(); // Adds a CampaignFeed that associates the feed with this campaign for // the AFFILIATE_LOCATION placeholder type. try (CampaignFeedServiceClient feedServiceClient = googleAdsClient.getLatestVersion().createCampaignFeedServiceClient()) { MutateCampaignFeedsResponse response = feedServiceClient.mutateCampaignFeeds( String.valueOf(customerId), ImmutableList.of(operation)); System.out.printf( "Campaign feed created with resource name: %s.%n", response.getResultsList().get(0).getResourceName()); } } /** Gets the feed item attribute ID that specifies the chain ID. */ private long getAttributeIdForChainId(FeedMapping feedMapping) { Optional<AttributeFieldMapping> fieldMapping = feedMapping.getAttributeFieldMappingsList().stream() .filter( m -> m.getAffiliateLocationField() == AffiliateLocationPlaceholderField.CHAIN_ID) .findFirst(); if (!fieldMapping.isPresent()) { throw new RuntimeException("Affiliate location field mapping isn't setup correctly"); } return fieldMapping.get().getFeedAttributeId(); } /** Removes a list of feeds from the customerID. */ private void removeFeeds(GoogleAdsClient googleAdsClient, long customerId, List<Feed> feeds) { List<FeedOperation> removeOperations = feeds.stream() .map(f -> FeedOperation.newBuilder().setRemove(f.getResourceName()).build()) .collect(Collectors.toList()); try (FeedServiceClient feedServiceClient = googleAdsClient.getLatestVersion().createFeedServiceClient()) { feedServiceClient.mutateFeeds(String.valueOf(customerId), removeOperations); } } /** Retrieves all location extension feeds from a specified customer ID. */ private List<Feed> getLocationExtensionFeeds(GoogleAdsClient googleAdsClient, long customerId) { String query = "SELECT" + " customer_feed.resource_name, " + " customer_feed.feed, " + " customer_feed.status, " + " customer_feed.matching_function.function_string " + "FROM" + " customer_feed " + "WHERE" + " customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION)" + " AND customer_feed.status = ENABLED"; try (GoogleAdsServiceClient client = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { SearchPagedResponse response = client.search(String.valueOf(customerId), query); List<Feed> results = new ArrayList(); response.iterateAll().forEach(row -> results.add(row.getFeed())); return results; } } /** Removes all feeds linked at the customer (global) level. */ private void removeCustomerFeeds( GoogleAdsClient googleAdsClient, long customerId, List<CustomerFeed> oldCustomerFeeds) { List<FeedOperation> operations = oldCustomerFeeds.stream() .map(f -> FeedOperation.newBuilder().setRemove(f.getResourceName()).build()) .collect(Collectors.toList()); if (!operations.isEmpty()) { try (FeedServiceClient feedServiceClient = googleAdsClient.getLatestVersion().createFeedServiceClient()) { feedServiceClient.mutateFeeds(String.valueOf(customerId), operations); } } } /** Retrieves the location extension feeds linked to a customer ID. */ private List<CustomerFeed> getLocationExtensionCustomerFeeds( GoogleAdsClient googleAdsClient, long customerId) { String query = "SELECT" + " customer_feed.resource_name, " + " customer_feed.feed, " + " customer_feed.status, " + " customer_feed.matching_function.function_string " + "FROM" + " customer_feed " + "WHERE" + " customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION) " + " AND customer_feed.status = ENABLED"; try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { SearchPagedResponse response = googleAdsServiceClient.search(String.valueOf(customerId), query); List<CustomerFeed> results = new ArrayList(); response.iterateAll().forEach(row -> results.add(row.getCustomerFeed())); return results; } } /** Waits for a feed (identified by it's feedResourceName) to become ready. */ private FeedMapping waitForFeedToBeReady( GoogleAdsClient googleAdsServiceClient, long customerId, String feedResourceName) { int numAttempts = 0; int sleepSeconds = 0; // Waits for Google's servers to setup the feed with feed attributes and attribute mapping. // This process is asynchronous, so we wait until the feed mapping is created, with exponential // backoff. while (numAttempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS) { Optional<FeedMapping> feedMapping = getFeedMapping(googleAdsServiceClient, customerId, feedResourceName); if (feedMapping.isPresent()) { System.out.printf("Feed with resource name '%s' is now ready.%n", feedResourceName); return feedMapping.get(); } else { numAttempts++; sleepSeconds = (int) (5 * Math.pow(2, numAttempts)); System.out.printf( "Checked: %d time(s). Feed is not ready " + "yet. Waiting %d seconds before trying again.%n", numAttempts, sleepSeconds); try { Thread.sleep(sleepSeconds * 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } } throw new ApiException( String.format("Feed is not ready after %d retries.", numAttempts), null, GrpcStatusCode.of(Code.DEADLINE_EXCEEDED), true); } /** Retrieves the {@link FeedMapping} for a given feed. */ private Optional<FeedMapping> getFeedMapping( GoogleAdsClient googleAdsServiceClient, long customerId, String feedResourceName) { String query = String.format( "SELECT" + " feed_mapping.resource_name, " + " feed_mapping.attribute_field_mappings, " + " feed_mapping.status " + "FROM" + " feed_mapping " + "WHERE feed_mapping.feed = '%s'" + " AND feed_mapping.status = ENABLED " + " AND feed_mapping.placeholder_type = AFFILIATE_LOCATION " + "LIMIT 1", feedResourceName); try (GoogleAdsServiceClient client = googleAdsServiceClient.getLatestVersion().createGoogleAdsServiceClient()) { SearchPagedResponse response = client.search(String.valueOf(customerId), query); Iterator<GoogleAdsRow> iterator = response.iterateAll().iterator(); if (iterator.hasNext()) { return Optional.of(iterator.next().getFeedMapping()); } return Optional.empty(); } } }
C#
// Copyright 2020 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.V15.Common; using Google.Ads.GoogleAds.V15.Errors; using Google.Ads.GoogleAds.V15.Resources; using Google.Ads.GoogleAds.V15.Services; using Google.Api.Gax; using Grpc.Core; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using static Google.Ads.GoogleAds.V15.Enums.AffiliateLocationFeedRelationshipTypeEnum.Types; using static Google.Ads.GoogleAds.V15.Enums.AffiliateLocationPlaceholderFieldEnum.Types; using static Google.Ads.GoogleAds.V15.Enums.FeedOriginEnum.Types; using static Google.Ads.GoogleAds.V15.Enums.PlaceholderTypeEnum.Types; using static Google.Ads.GoogleAds.V15.Resources.Feed.Types; namespace Google.Ads.GoogleAds.Examples.V15 { /// <summary> /// This code example adds a feed that syncs retail addresses for a given retail chain ID and /// associates the feed with a campaign for serving affiliate location extensions. /// </summary> public class AddAffiliateLocationExtensions : ExampleBase { /// <summary> /// Command line options for running the <see cref="AddAffiliateLocationExtensions"/> /// example. /// </summary> public class Options : OptionsBase { /// <summary> /// The customer ID for which the call is made. /// </summary> [Option("customerId", Required = true, HelpText = "The customer ID for which the call is made.")] public long CustomerId { get; set; } /// <summary> /// The retail chain ID. /// </summary> [Option("chainId", Required = true, HelpText = "The retail chain ID.")] public long ChainId { get; set; } /// <summary> /// The campaign ID for which the affiliate location extensions are added. /// </summary> [Option("campaignId", Required = true, HelpText = "The campaign ID for which the affiliate location extensions are added.")] public long CampaignId { get; set; } } /// <summary> /// Main method, to run this code example as a standalone application. /// </summary> /// <param name="args">The command line arguments.</param> public static void Main(string[] args) { Options options = ExampleUtilities.ParseCommandLine<Options>(args); AddAffiliateLocationExtensions codeExample = new AddAffiliateLocationExtensions(); Console.WriteLine(codeExample.Description); codeExample.Run(new GoogleAdsClient(), options.CustomerId, options.ChainId, options.CampaignId); } // The maximum number of attempts to make to retrieve the FeedMappings before throwing an // exception. private const int MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10; /// <summary> /// Returns a description about the code example. /// </summary> public override string Description => "This code example adds a feed that syncs retail addresses for a given retail chain " + "ID and associates the feed with a campaign for serving affiliate location extensions."; /// <summary> /// Runs the code example. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="chainId">The retail chain ID.</param> /// <param name="campaignId"> /// The campaign ID for which the affiliate location extensions are added. /// </param> public void Run(GoogleAdsClient client, long customerId, long chainId, long campaignId) { try { string feedResourceName = CreateAffiliateLocationExtensionFeed( client, customerId, chainId); // After the completion of the feed creation operation above the added feed will not // be available for usage in a campaign feed until the feed mappings are created. We // will wait with an exponential back-off policy until the feed mappings have been // created. FeedMapping feedMapping = WaitForFeedToBeReady(client, customerId, feedResourceName); CreateCampaignFeed(client, customerId, campaignId, feedMapping, feedResourceName, chainId); } catch (GoogleAdsException e) { Console.WriteLine("Failure:"); Console.WriteLine($"Message: {e.Message}"); Console.WriteLine($"Failure: {e.Failure}"); Console.WriteLine($"Request ID: {e.RequestId}"); throw; } } /// <summary> /// Creates the Affiliate Location Extension feed. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="chainId">The retail chain ID.</param> /// <returns>Resource name of the newly created Affiliate Location Extension feed.</returns> private static string CreateAffiliateLocationExtensionFeed(GoogleAdsClient client, long customerId, long chainId) { // Optional: Delete all existing location extension feeds. This is an optional step, and // is required for this code example to run correctly more than once. // 1. Google Ads only allows one location extension feed per email address. // 2. A Google Ads account cannot have a location extension feed and an affiliate // location extension feed at the same time. DeleteLocationExtensionFeeds(client, customerId); // Get the FeedServiceClient. FeedServiceClient feedService = client.GetService(Services.V15.FeedService); // Creates a feed that will sync to retail addresses for a given retail chain ID. Do not // add FeedAttributes to this object as Google Ads will add them automatically because // this will be a system generated feed. Feed feed = new Feed() { Name = "Affiliate Location Extension feed #" + ExampleUtilities.GetRandomString(), AffiliateLocationFeedData = new AffiliateLocationFeedData() { ChainIds = { chainId }, RelationshipType = AffiliateLocationFeedRelationshipType.GeneralRetailer }, // Since this feed's contents will be managed by Google, you must set its origin to // GOOGLE. Origin = FeedOrigin.Google }; FeedOperation operation = new FeedOperation() { Create = feed }; // Adds the feed. MutateFeedsResponse response = feedService.MutateFeeds(customerId.ToString(), new[] { operation }); // Displays the results. string feedResourceName = response.Results[0].ResourceName; Console.WriteLine($"Affliate location extension feed created with resource name: " + $"{feedResourceName}."); return feedResourceName; } /// <summary> /// Deletes the old location extension feeds. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> private static void DeleteLocationExtensionFeeds(GoogleAdsClient client, long customerId) { // To delete a location extension feed, you need to // 1. Delete the CustomerFeed so that the location extensions from the feed stop // serving. // 2. Delete the feed so that Google Ads will no longer sync from the Business Profile // account. CustomerFeed[] oldCustomerFeeds = GetLocationExtensionCustomerFeeds(client, customerId); if (oldCustomerFeeds.Length != 0) { // Optional: You may also want to delete the CampaignFeeds and AdGroupFeeds. DeleteCustomerFeeds(client, customerId, oldCustomerFeeds); } Feed[] feeds = GetLocationExtensionFeeds(client, customerId); if (feeds.Length != 0) { RemoveFeeds(client, customerId, feeds); } } /// <summary> /// Gets the location extension feeds. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <returns>The list of location extension feeds.</returns> private static Feed[] GetLocationExtensionFeeds(GoogleAdsClient client, long customerId) { List<Feed> feeds = new List<Feed>(); GoogleAdsServiceClient googleAdsService = client.GetService( Services.V15.GoogleAdsService); // Create the query. string query = $"SELECT feed.resource_name, feed.status, " + $"feed.places_location_feed_data.email_address, " + $"feed.affiliate_location_feed_data.chain_ids " + $" from feed where feed.status = ENABLED"; PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result = googleAdsService.Search(customerId.ToString(), query); foreach (GoogleAdsRow row in result) { // A location extension feed can be identified by checking whether the // PlacesLocationFeedData field is set (Location extensions feeds) or // AffiliateLocationFeedData field is set (Affiliate location extension feeds) Feed feed = row.Feed; if (feed.PlacesLocationFeedData != null || feed.AffiliateLocationFeedData != null) { feeds.Add(feed); } } return feeds.ToArray(); } /// <summary> /// Removes the feeds. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="feeds">The list of feeds to remove.</param> private static void RemoveFeeds(GoogleAdsClient client, long customerId, Feed[] feeds) { List<FeedOperation> operations = new List<FeedOperation>(); foreach (Feed feed in feeds) { FeedOperation operation = new FeedOperation() { Remove = feed.ResourceName, }; operations.Add(operation); } FeedServiceClient feedService = client.GetService( Services.V15.FeedService); feedService.MutateFeeds(customerId.ToString(), operations.ToArray()); } /// <summary> /// Gets the location extension customer feeds. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <returns>The list of location extension customer feeds.</returns> private static CustomerFeed[] GetLocationExtensionCustomerFeeds(GoogleAdsClient client, long customerId) { List<CustomerFeed> customerFeeds = new List<CustomerFeed>(); GoogleAdsServiceClient googleAdsService = client.GetService( Services.V15.GoogleAdsService); // Create the query. A location extension customer feed can be identified by filtering // for placeholder_types=LOCATION (location extension feeds) or placeholder_types // =AFFILIATE_LOCATION (affiliate location extension feeds) string query = $"SELECT customer_feed.resource_name, customer_feed.feed, " + $"customer_feed.status, customer_feed.matching_function.function_string from " + $"customer_feed " + $"WHERE customer_feed.placeholder_types CONTAINS " + $"ANY(LOCATION, AFFILIATE_LOCATION) and customer_feed.status=ENABLED"; PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result = googleAdsService.Search(customerId.ToString(), query); foreach (GoogleAdsRow row in result) { customerFeeds.Add(row.CustomerFeed); } return customerFeeds.ToArray(); } /// <summary> /// Deletes the customer feeds. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="customerFeeds">The customer feeds to delete.</param> private static void DeleteCustomerFeeds(GoogleAdsClient client, long customerId, CustomerFeed[] customerFeeds) { List<CustomerFeedOperation> operations = new List<CustomerFeedOperation>(); foreach (CustomerFeed customerFeed in customerFeeds) { CustomerFeedOperation operation = new CustomerFeedOperation() { Remove = customerFeed.ResourceName, }; operations.Add(operation); } CustomerFeedServiceClient feedService = client.GetService( Services.V15.CustomerFeedService); feedService.MutateCustomerFeeds(customerId.ToString(), operations.ToArray()); } /// <summary> /// Gets the Affiliate Location Extension feed mapping. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="feedResourceName">The feed resource name.</param> /// <returns>The newly created feed mapping.</returns> private static FeedMapping GetAffiliateLocationExtensionFeedMapping(GoogleAdsClient client, long customerId, string feedResourceName) { // Get the GoogleAdsService. GoogleAdsServiceClient googleAdsService = client.GetService( Services.V15.GoogleAdsService); // Create the query. string query = $"SELECT feed_mapping.resource_name, " + $"feed_mapping.attribute_field_mappings, feed_mapping.status FROM " + $"feed_mapping WHERE feed_mapping.feed = '{feedResourceName}' and " + $"feed_mapping.status = ENABLED and feed_mapping.placeholder_type = " + "AFFILIATE_LOCATION LIMIT 1"; // Issue a search request. PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result = googleAdsService.Search(customerId.ToString(), query); // Display the results. GoogleAdsRow googleAdsRow = result.FirstOrDefault(); return (googleAdsRow == null) ? null : googleAdsRow.FeedMapping; } /// <summary> /// Waits for the Affliate location extension feed to be ready. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="feedResourceName">Resource name of the feed.</param> private static FeedMapping WaitForFeedToBeReady(GoogleAdsClient client, long customerId, string feedResourceName) { int numAttempts = 0; int sleepSeconds = 0; while (numAttempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS) { // Once you create a feed, Google's servers will setup the feed by creating feed // attributes and feed mapping. Once the feed mapping is created, it is ready to be // used for creating customer feed. This process is asynchronous, so we wait until // the feed mapping is created, peforming exponential backoff. FeedMapping feedMapping = GetAffiliateLocationExtensionFeedMapping( client, customerId, feedResourceName); if (feedMapping == null) { numAttempts++; sleepSeconds = (int) (5 * Math.Pow(2, numAttempts)); Console.WriteLine($"Checked: #{numAttempts} time(s). Feed is not ready " + $"yet. Waiting {sleepSeconds} seconds before trying again."); Thread.Sleep(sleepSeconds * 1000); } else { Console.WriteLine($"Feed {feedResourceName} is now ready."); return feedMapping; } } throw new RpcException(new Status(StatusCode.DeadlineExceeded, $"Feed is not ready after {MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS}" + $" retries.")); } /// <summary> /// Creates the campaign feed. /// </summary> /// <param name="client">The Google Ads client.</param> /// <param name="customerId">The customer ID for which the call is made.</param> /// <param name="campaignId"> /// The campaign ID for which the affiliate location extensions are added. /// </param> /// <param name="feedMapping"> /// The affliate location extension feedmapping for <paramref name="feedResourceName"/> /// </param> /// <param name="feedResourceName">The feed resource name.</param> /// <param name="chainId">The retail chain ID.</param> private static void CreateCampaignFeed(GoogleAdsClient client, long customerId, long campaignId, FeedMapping feedMapping, string feedResourceName, long chainId) { // Get the CampaignFeedService. CampaignFeedServiceClient campaignFeedService = client.GetService( Services.V15.CampaignFeedService); long attributeIdForChainId = GetAttributeIdForChainId(feedMapping); string feedId = FeedName.Parse(feedResourceName).FeedId; string matchingFunction = $"IN(FeedAttribute[{feedId}, {attributeIdForChainId}], {chainId})"; // Adds a CampaignFeed that associates the feed with this campaign for the // AFFILIATE_LOCATION placeholder type. CampaignFeed campaignFeed = new CampaignFeed() { Feed = feedResourceName, PlaceholderTypes = { PlaceholderType.AffiliateLocation }, MatchingFunction = new MatchingFunction() { FunctionString = matchingFunction }, Campaign = ResourceNames.Campaign(customerId, campaignId), }; CampaignFeedOperation operation = new CampaignFeedOperation() { Create = campaignFeed }; MutateCampaignFeedsResponse campaignFeedsResponse = campaignFeedService.MutateCampaignFeeds( customerId.ToString(), new[] { operation }); // Displays the result. string addedCampaignFeed = campaignFeedsResponse.Results[0].ResourceName; Console.WriteLine($"Campaign feed created with resource name: {addedCampaignFeed}."); return; } /// <summary> /// Gets the feed attribute ID for the retail chain Id. /// </summary> /// <param name="feedMapping">The feed mapping.</param> /// <returns>The feeed attribute ID.</returns> /// <exception cref="ArgumentException"> /// Affiliate location feed mapping isn't setup correctly. /// </exception> public static long GetAttributeIdForChainId(FeedMapping feedMapping) { foreach (AttributeFieldMapping fieldMapping in feedMapping.AttributeFieldMappings) { if (fieldMapping.AffiliateLocationField == AffiliateLocationPlaceholderField.ChainId) { return fieldMapping.FeedAttributeId; } } throw new ArgumentException("Affiliate location feed mapping isn't setup correctly."); } } }
2,399필리핀
<?php /** * Copyright 2020 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\Extensions; 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\Examples\Utils\Helper; use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder; use Google\Ads\GoogleAds\Lib\V15\GoogleAdsClient; use Google\Ads\GoogleAds\Lib\V15\GoogleAdsClientBuilder; use Google\Ads\GoogleAds\Lib\V15\GoogleAdsException; use Google\Ads\GoogleAds\Lib\V15\GoogleAdsServerStreamDecorator; use Google\Ads\GoogleAds\Util\V15\ResourceNames; use Google\Ads\GoogleAds\V15\Common\MatchingFunction; use Google\Ads\GoogleAds\V15\Enums\AffiliateLocationFeedRelationshipTypeEnum\AffiliateLocationFeedRelationshipType; use Google\Ads\GoogleAds\V15\Enums\AffiliateLocationPlaceholderFieldEnum\AffiliateLocationPlaceholderField; use Google\Ads\GoogleAds\V15\Enums\FeedOriginEnum\FeedOrigin; use Google\Ads\GoogleAds\V15\Enums\PlaceholderTypeEnum\PlaceholderType; use Google\Ads\GoogleAds\V15\Errors\GoogleAdsError; use Google\Ads\GoogleAds\V15\Resources\AttributeFieldMapping; use Google\Ads\GoogleAds\V15\Resources\CampaignFeed; use Google\Ads\GoogleAds\V15\Resources\CustomerFeed; use Google\Ads\GoogleAds\V15\Resources\Feed; use Google\Ads\GoogleAds\V15\Resources\Feed\AffiliateLocationFeedData; use Google\Ads\GoogleAds\V15\Resources\FeedMapping; use Google\Ads\GoogleAds\V15\Services\CampaignFeedOperation; use Google\Ads\GoogleAds\V15\Services\CustomerFeedOperation; use Google\Ads\GoogleAds\V15\Services\FeedOperation; use Google\Ads\GoogleAds\V15\Services\FeedServiceClient; use Google\Ads\GoogleAds\V15\Services\GoogleAdsRow; use Google\Ads\GoogleAds\V15\Services\MutateCampaignFeedsRequest; use Google\Ads\GoogleAds\V15\Services\MutateCustomerFeedsRequest; use Google\Ads\GoogleAds\V15\Services\MutateFeedsRequest; use Google\Ads\GoogleAds\V15\Services\SearchGoogleAdsRequest; use Google\Ads\GoogleAds\V15\Services\SearchGoogleAdsStreamRequest; use Google\ApiCore\ApiException; use RuntimeException; /** * This code example adds a feed that syncs retail addresses for a given retail chain ID and * associates the feed with a campaign for serving affiliate location extensions. */ class AddAffiliateLocationExtensions { private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE'; // The retail chain ID. For a complete list of valid retail chain IDs, see // https://developers.google.com/adwords/api/docs/appendix/codes-formats#chain-ids. private const CHAIN_ID = 'INSERT_CHAIN_ID_HERE'; // The campaign ID for which the affiliate location extensions are added. private const CAMPAIGN_ID = 'INSERT_CAMPAIGN_ID_HERE'; // Optional: Delete all existing location extension feeds. This is required for this code // example to run correctly more than once. // 1. Google Ads only allows one location extension feed per email address. // 2. A Google Ads account cannot have a location extension feed and an affiliate location // extension feed at the same time. private const DELETE_EXISTING_FEEDS = false; // The maximum number of attempts to make to retrieve the feed mapping before throwing an // exception. private const MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS = 10; public static function main() { // Either pass the required parameters for this example on the command line, or insert them // into the constants above. $options = (new ArgumentParser())->parseCommandArguments([ ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT, ArgumentNames::CHAIN_ID => GetOpt::REQUIRED_ARGUMENT, ArgumentNames::CAMPAIGN_ID => GetOpt::REQUIRED_ARGUMENT, ArgumentNames::DELETE_EXISTING_FEEDS => GetOpt::OPTIONAL_ARGUMENT ]); // Generate a refreshable OAuth2 credential for authentication. $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build(); // Construct a Google Ads client configured from a properties file and the // OAuth2 credentials above. $googleAdsClient = (new GoogleAdsClientBuilder()) ->fromFile() ->withOAuth2Credential($oAuth2Credential) // We set this value to true to show how to use GAPIC v2 source code. You can remove the // below line if you wish to use the old-style source code. Note that in that case, you // probably need to modify some parts of the code below to make it work. // For more information, see // https://developers.devsite.corp.google.com/google-ads/api/docs/client-libs/php/gapic. ->usingGapicV2Source(true) ->build(); try { self::runExample( $googleAdsClient, $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID, $options[ArgumentNames::CHAIN_ID] ?: self::CHAIN_ID, $options[ArgumentNames::CAMPAIGN_ID] ?: self::CAMPAIGN_ID, filter_var( $options[ArgumentNames::DELETE_EXISTING_FEEDS] ?: self::DELETE_EXISTING_FEEDS, 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 $chainId the retail chain ID * @param int $campaignId the campaign ID for which the affiliate location extensions are added * @param bool $shouldDeleteExistingFeeds true if it should delete the existing feeds. The * example will throw an error if feeds already exist and this option is not set to true */ public static function runExample( GoogleAdsClient $googleAdsClient, int $customerId, int $chainId, int $campaignId, bool $shouldDeleteExistingFeeds ) { if ($shouldDeleteExistingFeeds) { self::deleteLocationExtensionFeeds($googleAdsClient, $customerId); } $feedResourceName = self::createAffiliateLocationExtensionFeed( $googleAdsClient, $customerId, $chainId ); // After the completion of the feed creation operation above the added feed will not // be available for usage in a campaign feed until the feed mapping is created. // We then need to wait for the feed mapping to be created. $feedMapping = self::waitForFeedToBeReady( $googleAdsClient, $customerId, $feedResourceName ); self::createCampaignFeed( $googleAdsClient, $customerId, $campaignId, $feedMapping, $feedResourceName, $chainId ); } /** * Deletes the existing location extension feeds. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID */ private static function deleteLocationExtensionFeeds( GoogleAdsClient $googleAdsClient, int $customerId ) { // To delete a location extension feed, you need to // 1. Delete the customer feed so that the location extensions from the feed stop serving. // 2. Delete the feed so that Google Ads will no longer sync from the Business Profile // account. $customerFeeds = self::getLocationExtensionCustomerFeeds($googleAdsClient, $customerId); if (!empty($customerFeeds)) { // Optional: You may also want to delete the campaign and ad group feeds. self::removeCustomerFeeds($googleAdsClient, $customerId, $customerFeeds); } $feeds = self::getLocationExtensionFeeds($googleAdsClient, $customerId); if (!empty($feeds)) { self::removeFeeds($googleAdsClient, $customerId, $feeds); } } /** * Gets the existing location extension customer feeds. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @return CustomerFeed[] the list of location extension customer feeds */ private static function getLocationExtensionCustomerFeeds( GoogleAdsClient $googleAdsClient, int $customerId ): array { $customerFeeds = []; $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); // Creates the query. A location extension customer feed can be identified by filtering // for placeholder_types as LOCATION (location extension feeds) or // placeholder_types as AFFILIATE_LOCATION (affiliate location extension feeds). $query = 'SELECT customer_feed.resource_name ' . 'FROM customer_feed ' . 'WHERE customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION) ' . 'AND customer_feed.status = ENABLED'; // Issues a search stream request. /** @var GoogleAdsServerStreamDecorator $stream */ $stream = $googleAdsServiceClient->searchStream( SearchGoogleAdsStreamRequest::build($customerId, $query) ); // Iterates over all rows in all messages to collect the results. foreach ($stream->iterateAllElements() as $googleAdsRow) { /** @var GoogleAdsRow $googleAdsRow */ $customerFeeds[] = $googleAdsRow->getCustomerFeed(); } return $customerFeeds; } /** * Removes the customer feeds. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param CustomerFeed[] $customerFeeds the list of customer feeds to remove */ private static function removeCustomerFeeds( GoogleAdsClient $googleAdsClient, int $customerId, array $customerFeeds ) { $operations = []; foreach ($customerFeeds as $customerFeed) { /** @var CustomerFeed $customerFeed */ $operations[] = new CustomerFeedOperation([ 'remove' => $customerFeed->getResourceName() ]); } // Issues a mutate request to remove the customer feeds. $googleAdsClient->getCustomerFeedServiceClient()->mutateCustomerFeeds( MutateCustomerFeedsRequest::build($customerId, $operations) ); } /** * Gets the existing location extension feeds. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @return Feed[] the list of location extension feeds */ private static function getLocationExtensionFeeds( GoogleAdsClient $googleAdsClient, int $customerId ): array { $feeds = []; $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); // Creates the query. $query = 'SELECT feed.resource_name ' . 'FROM feed ' . 'WHERE feed.status = ENABLED ' . 'AND feed.origin = USER'; // Issues a search stream request. /** @var GoogleAdsServerStreamDecorator $stream */ $stream = $googleAdsServiceClient->searchStream( SearchGoogleAdsStreamRequest::build($customerId, $query) ); // Iterates over all rows in all messages to collect the results. foreach ($stream->iterateAllElements() as $googleAdsRow) { /** @var GoogleAdsRow $googleAdsRow */ $feeds[] = $googleAdsRow->getFeed(); } return $feeds; } /** * Removes the feeds. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param Feed[] $feeds the list of feeds to remove */ private static function removeFeeds( GoogleAdsClient $googleAdsClient, int $customerId, array $feeds ) { $operations = []; foreach ($feeds as $feed) { /** @var Feed $feed */ $operations[] = new FeedOperation([ 'remove' => $feed->getResourceName() ]); } // Issues a mutate request to remove the feeds. $googleAdsClient->getFeedServiceClient()->mutateFeeds( MutateFeedsRequest::build($customerId, $operations) ); } /** * Creates the affiliate location extension feed. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param int $chainId the retail chain ID * @return string the resource name of the newly created affiliate location extension feed */ private static function createAffiliateLocationExtensionFeed( GoogleAdsClient $googleAdsClient, int $customerId, int $chainId ): string { // Creates a feed that will sync to retail addresses for a given retail chain ID. // Do not add feed attributes, Google Ads will add them automatically because this will // be a system generated feed. $feed = new Feed([ 'name' => 'Affiliate Location Extension feed #' . Helper::getPrintableDatetime(), 'affiliate_location_feed_data' => new AffiliateLocationFeedData([ 'chain_ids' => [$chainId], 'relationship_type' => AffiliateLocationFeedRelationshipType::GENERAL_RETAILER ]), // Since this feed's contents will be managed by Google, you must set its origin to // GOOGLE. 'origin' => FeedOrigin::GOOGLE ]); // Creates the feed operation. $operation = new FeedOperation(); $operation->setCreate($feed); // Issues a mutate request to add the feed and prints some information. $feedServiceClient = $googleAdsClient->getFeedServiceClient(); $response = $feedServiceClient->mutateFeeds(MutateFeedsRequest::build($customerId, [$operation])); $feedResourceName = $response->getResults()[0]->getResourceName(); printf( "Affiliate location extension feed created with resource name: '%s'.%s", $feedResourceName, PHP_EOL ); return $feedResourceName; } /** * Waits for the affiliate location extension feed to be ready. An exponential back-off * policy with a maximum number of attempts is used to poll the server. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param string $feedResourceName the feed resource name * @throws RuntimeException if the feed mapping is still not ready and the maximum number of * attempts has been reached * @return FeedMapping the newly created feed mapping */ private static function waitForFeedToBeReady( GoogleAdsClient $googleAdsClient, int $customerId, string $feedResourceName ): FeedMapping { $numAttempts = 0; while ($numAttempts < self::MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS) { // Once you create a feed, Google's servers will setup the feed by creating feed // attributes and feed mapping. Once the feed mapping is created, it is ready to be // used for creating customer feed. // This process is asynchronous, so we wait until the feed mapping is created, // performing exponential backoff. $feedMapping = self::getAffiliateLocationExtensionFeedMapping( $googleAdsClient, $customerId, $feedResourceName ); if (is_null($feedMapping)) { $numAttempts++; $sleepSeconds = intval(5 * pow(2, $numAttempts)); printf( 'Checked: %d time(s). Feed is not ready yet. ' . 'Waiting %d seconds before trying again.%s', $numAttempts, $sleepSeconds, PHP_EOL ); sleep($sleepSeconds); } else { printf("Feed '%s' is now ready.%s", $feedResourceName, PHP_EOL); return $feedMapping; } } throw new RuntimeException(sprintf( "The affiliate location feed mapping is still not ready after %d attempt(s).%s", self::MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS, PHP_EOL )); } /** * Gets the affiliate location extension feed mapping. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param string $feedResourceName the feed resource name * @return FeedMapping|null the feed mapping if it exists otherwise null */ private static function getAffiliateLocationExtensionFeedMapping( GoogleAdsClient $googleAdsClient, int $customerId, string $feedResourceName ): ?FeedMapping { $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); // Creates a query that retrieves the feed mapping. $query = sprintf( "SELECT feed_mapping.resource_name, " . "feed_mapping.attribute_field_mappings, " . "feed_mapping.status " . "FROM feed_mapping " . "WHERE feed_mapping.feed = '%s' " . "AND feed_mapping.status = ENABLED " . "AND feed_mapping.placeholder_type = AFFILIATE_LOCATION " . "LIMIT 1", $feedResourceName ); // Issues a search request. $response = $googleAdsServiceClient->search( SearchGoogleAdsRequest::build($customerId, $query)->setReturnTotalResultsCount(true) ); return $response->getPage()->getPageElementCount() === 1 ? $response->getIterator()->current()->getFeedMapping() : null; } /** * Creates the campaign feed. * * @param GoogleAdsClient $googleAdsClient the Google Ads API client * @param int $customerId the customer ID * @param int $campaignId the campaign ID for which the affiliate location extensions are added * @param FeedMapping $feedMapping the affiliate location extension feed mapping * @param string $feedResourceName the feed resource name * @param int $chainId the retail chain ID */ private static function createCampaignFeed( GoogleAdsClient $googleAdsClient, int $customerId, int $campaignId, FeedMapping $feedMapping, string $feedResourceName, int $chainId ) { $matchingFunction = sprintf( 'IN(FeedAttribute[%d, %d], %d)', FeedServiceClient::parseName($feedResourceName)['feed'], self::getAttributeIdForChainId($feedMapping), $chainId ); // Adds a campaign feed that associates the feed with this campaign for the // AFFILIATE_LOCATION placeholder type. $campaignFeed = new CampaignFeed([ 'feed' => $feedResourceName, 'placeholder_types' => [PlaceholderType::AFFILIATE_LOCATION], 'matching_function' => new MatchingFunction(['function_string' => $matchingFunction]), 'campaign' => ResourceNames::forCampaign($customerId, $campaignId) ]); // Creates the campaign feed operation. $operation = new CampaignFeedOperation(); $operation->setCreate($campaignFeed); // Issues a mutate request to add the campaign feed and prints some information. $campaignFeedServiceClient = $googleAdsClient->getCampaignFeedServiceClient(); $response = $campaignFeedServiceClient->mutateCampaignFeeds( MutateCampaignFeedsRequest::build($customerId, [$operation]) ); printf( "Campaign feed created with resource name: '%s'.%s", $response->getResults()[0]->getResourceName(), PHP_EOL ); } /** * Gets the feed attribute ID for the retail chain ID. * * @param FeedMapping $feedMapping the feed mapping * @@return int the feed attribute ID */ private static function getAttributeIdForChainId(FeedMapping $feedMapping): int { foreach ($feedMapping->getAttributeFieldMappings() as $fieldMapping) { /** @var AttributeFieldMapping $fieldMapping */ if ( $fieldMapping->getAffiliateLocationField() === AffiliateLocationPlaceholderField::CHAIN_ID ) { return $fieldMapping->getFeedAttributeId(); } } throw new RuntimeException( "Affiliate location feed mapping isn't setup correctly." . PHP_EOL ); } } AddAffiliateLocationExtensions::main();
Python
#!/usr/bin/env python # Copyright 2020 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. """Demonstrates how to add Affiliate Location extensions. This example adds a feed that syncs retail addresses for a given retail chain ID and associates the feed with a campaign for serving affiliate location extensions. """ import argparse import sys from time import sleep from uuid import uuid4 from google.ads.googleads.client import GoogleAdsClient from google.ads.googleads.errors import GoogleAdsException # The maximum number of attempts to retrieve the FeedMappings before throwing an # exception. MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10 def main(client, customer_id, chain_id, campaign_id): """Demonstrates how to add Affiliate Location extensions. Args: client: An initialized instance. customer_id: The client customer ID. chain_id: The retail chain ID. See: https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids for a complete list of valid retail chain IDs. campaign_id: The campaign ID for which the affiliate location extensions will be added. """ feed_resource_name = create_affiliate_location_extension_feed( client, customer_id, chain_id ) # After the completion of the feed creation operation above, the added # feed will not be available for usage in a campaign feed until the feed # mappings are created. We will wait with an exponential back-off policy # until the feed mappings have been created. feed_mapping = wait_for_feed_to_be_ready( client, customer_id, feed_resource_name ) create_campaign_feed( client, customer_id, campaign_id, feed_mapping, feed_resource_name, chain_id, ) def create_affiliate_location_extension_feed(client, customer_id, chain_id): """Creates the Affiliate Location Extension feed. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. chain_id: The retail chain ID. Returns: The string resource name of the newly created Affiliate Location Extension feed. """ # Optional: Remove all existing location extension feeds. This is an # optional step, and is required for this code example to run correctly more # than once. # This is because Google Ads only allows one location extension feed per # email address, and a Google Ads account cannot have a location extension # feed and an affiliate location extension feed at the same time. remove_location_extension_feeds(client, customer_id) # Get the FeedServiceClient. feed_service = client.get_service("FeedService") # Create a feed that will sync to retail addresses for a given retail chain # ID. Do not add FeedAttributes to this object as Google Ads will add # them automatically as this will be a system generated feed. feed_operation = client.get_type("FeedOperation") feed = feed_operation.create feed.name = f"Affiliate Location Extension feed #{uuid4()}" feed.affiliate_location_feed_data.chain_ids.append(chain_id) feed.affiliate_location_feed_data.relationship_type = ( client.enums.AffiliateLocationFeedRelationshipTypeEnum.GENERAL_RETAILER ) # Since this feed's contents will be managed by Google, you must set its # origin to GOOGLE. feed.origin = client.enums.FeedOriginEnum.GOOGLE # Add the feed. mutate_feeds_response = feed_service.mutate_feeds( customer_id=customer_id, operations=[feed_operation] ) # Display the results. feed_resource_name = mutate_feeds_response.results[0].resource_name print( "Affiliate location extension feed created with resource name: " f"{feed_resource_name}." ) return feed_resource_name def remove_location_extension_feeds(client, customer_id): """Removes the old location extension feeds. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. """ # To remove a location extension feed, you need to: # 1. Remove the CustomerFeed so that the location extensions from the feed # stop serving. # 2. Remove the feed so that Google Ads will no longer sync from the # Business Profile account. # Optional: You may also want to remove the CampaignFeeds and AdGroupFeeds. old_customer_feeds = get_location_extension_customer_feeds( client, customer_id ) if old_customer_feeds: remove_customer_feeds(client, customer_id, old_customer_feeds) feeds = get_location_extension_feeds(client, customer_id) if feeds: remove_feeds(client, customer_id, feeds) def get_location_extension_feeds(client, customer_id): """Gets the location extension feeds. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. Returns: The list of location extension feeds. """ googleads_service = client.get_service("GoogleAdsService") # Create the query. query = """ SELECT feed.resource_name, feed.status, feed.places_location_feed_data.email_address, feed.affiliate_location_feed_data.chain_ids FROM feed WHERE feed.status = ENABLED""" search_results = googleads_service.search( customer_id=customer_id, query=query ) # A location extension feed can be identified by checking whether the # PlacesLocationFeedData field is set (Location extensions feeds) or # AffiliateLocationFeedData field is set (Affiliate location extension # feeds) return [ row.feed for row in search_results if row.feed.places_location_feed_data or row.feed.affiliate_location_feed_data ] def remove_feeds(client, customer_id, feeds): """Removes the feeds. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. feeds: The list of feeds to remove. """ operations = [] for feed in feeds: operation = client.get_type("FeedOperation") operation.remove = feed.resource_name operations.append(operation) feed_service = client.get_service("FeedService") feed_service.mutate_feeds(customer_id=customer_id, operations=operations) def get_location_extension_customer_feeds(client, customer_id): """Gets the location extension customer feeds. Args: client: The Google Ads API client. customer_id: The customer ID from which to fetch feeds. Returns: A list of location extension customer feeds. """ googleads_service = client.get_service("GoogleAdsService") # Create the query. A location extension customer feed can be identified by # filtering for placeholder_types=LOCATION (location extension feeds) or # placeholder_types =AFFILIATE_LOCATION (affiliate location extension feeds) query = """ SELECT customer_feed.resource_name, customer_feed.feed, customer_feed.status, customer_feed.matching_function.function_string FROM customer_feed WHERE customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION) AND customer_feed.status=ENABLED""" search_results = googleads_service.search( customer_id=customer_id, query=query ) return [row.customer_feed for row in search_results] def remove_customer_feeds(client, customer_id, customer_feeds): """Removes the customer feeds. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. customer_feeds: The customer feeds to remove. """ operations = [] for customer_feed in customer_feeds: operation = client.get_type("CustomerFeedOperation") operation.remove = customer_feed.resource_name operations.append(operation) customer_feed_service = client.get_service("CustomerFeedService") customer_feed_service.mutate_customer_feeds( customer_id=customer_id, operations=operations ) def get_affiliate_location_extension_feed_mapping( client, customer_id, feed_resource_name ): """Gets the Affiliate Location Extension feed mapping. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. feed_resource_name: The feed resource name. Returns: The newly created feed mapping. """ # Get the GoogleAdsService. googleads_service = client.get_service("GoogleAdsService") # Create the query. query = f""" SELECT feed_mapping.resource_name, feed_mapping.attribute_field_mappings, feed_mapping.status FROM feed_mapping WHERE feed_mapping.feed = '{feed_resource_name}' AND feed_mapping.status = ENABLED AND feed_mapping.placeholder_type = AFFILIATE_LOCATION LIMIT 1""" # Issue a search request. search_results = googleads_service.search( customer_id=customer_id, query=query ) # Return the feed mapping that results from the search. # It is possible that the feed is not yet ready, so we catch the exception # if the feed mapping is not yet available. try: row = next(iter(search_results)) except StopIteration: return None else: return row.feed_mapping def wait_for_feed_to_be_ready(client, customer_id, feed_resource_name): """Waits for the Affiliate location extension feed to be ready. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. feed_resource_name: The resource name of the feed. Returns: The newly created FeedMapping. Raises: Exception: if the feed is not ready after the specified number of retries. """ num_attempts = 0 sleep_seconds = 0 while num_attempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS: # Once you create a feed, Google's servers will setup the feed by # creating feed attributes and feed mapping. Once the feed mapping is # created, it is ready to be used for creating customer feed. # This process is asynchronous, so we wait until the feed mapping is # created, performing exponential backoff. feed_mapping = get_affiliate_location_extension_feed_mapping( client, customer_id, feed_resource_name ) if feed_mapping is None: num_attempts += 1 sleep_seconds = 5 * 2**num_attempts print( f"Checked {num_attempts} time(s). Feed is not ready " f"yet. Waiting {sleep_seconds} seconds before trying again." ) sleep(sleep_seconds) else: print(f"Feed {feed_resource_name} is now ready.") return feed_mapping raise Exception( f"Feed is not ready after " f"{MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS} retries." ) def create_campaign_feed( client, customer_id, campaign_id, feed_mapping, feed_resource_name, chain_id ): """Creates the campaign feed. Args: client: The Google Ads API client. customer_id: The Google Ads customer ID. campaign_id: The campaign ID to which the affiliate location extensions will be added. feed_mapping: The affiliate location extension feedmapping for the feed resource name. feed_resource_name: The feed resource name. chain_id: The retail chain ID. """ # Get the CampaignFeedService. campaign_feed_service = client.get_service("CampaignFeedService") feed_service = client.get_service("FeedService") attribute_id_for_chain_id = get_attribute_id_for_chain_id( client, feed_mapping ) feed_id = feed_service.parse_feed_path(feed_resource_name)["feed_id"] matching_function = ( f"IN(FeedAttribute[{feed_id}, {attribute_id_for_chain_id}], {chain_id})" ) # Add a CampaignFeed that associates the feed with this campaign for # the AFFILIATE_LOCATION placeholder type. campaign_feed_operation = client.get_type("CampaignFeedOperation") campaign_feed = campaign_feed_operation.create campaign_feed.feed = feed_resource_name campaign_feed.placeholder_types.append( client.enums.PlaceholderTypeEnum.AFFILIATE_LOCATION ) campaign_feed.matching_function.function_string = matching_function campaign_feed.campaign = client.get_service( "CampaignService" ).campaign_path(customer_id, campaign_id) mutate_campaign_feeds_response = ( campaign_feed_service.mutate_campaign_feeds( customer_id=customer_id, operations=[campaign_feed_operation] ) ) # Display the result. print( "Campaign feed created with resource name: " f"{mutate_campaign_feeds_response.results[0].resource_name}." ) def get_attribute_id_for_chain_id(client, feed_mapping): """Gets the feed attribute ID for the retail chain ID. Args: client: The Google Ads API client. feed_mapping: The FeedMapping in which to search. Returns: The feed attribute ID. Raises: Exception: If no AffiliateLocationField with a retail chain ID is found in the FeedMapping. """ for field_mapping in feed_mapping.attribute_field_mappings: if ( field_mapping.affiliate_location_field == client.enums.AffiliateLocationPlaceholderFieldEnum.CHAIN_ID ): return field_mapping.feed_attribute_id raise Exception( "No AffiliateLocationField with a retail chain ID was " "found in the FeedMapping." ) if __name__ == "__main__": # will read the google-ads.yaml configuration file in the # home directory if none is specified. googleads_client = GoogleAdsClient.load_from_storage(version="v15") parser = argparse.ArgumentParser( description="Demonstrates how to add Affiliate Location extensions." ) # 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", "--chain_id", type=int, required=True, help="The retail chain ID. See: " "https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids " "for a complete list of valid retail chain IDs.", ) parser.add_argument( "-i", "--campaign_id", type=int, required=True, help="The campaign ID for which the affiliate location extensions will " "be added.", ) args = parser.parse_args() try: main( googleads_client, args.customer_id, args.chain_id, args.campaign_id, ) 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)
Ruby
#!/usr/bin/env ruby # Encoding: utf-8 # # Copyright 2020 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 code example adds a feed that syncs retail addresses for a given retail # chain ID and associates the feed with a campaign for serving affiliate # location extensions. require 'optparse' require 'google/ads/google_ads' require 'date' def add_affiliate_location_extensions( customer_id, chain_id, campaign_id, should_delete_existing_feeds) # GoogleAdsClient will read a config file from # ENV['HOME']/google_ads_config.rb when called without parameters client = Google::Ads::GoogleAds::GoogleAdsClient.new if should_delete_existing_feeds delete_location_extension_feeds(client, customer_id) end feed_resource_name = create_affiliate_location_extension_feed( client, customer_id, chain_id, ) # After the completion of the feed creation operation above the added feed # will not be available for usage in a campaign feed until the feed mapping # is created. We then need to wait for the feed mapping to be created. feed_mapping = wait_for_feed_to_be_ready( client, customer_id, feed_resource_name, ) create_campaign_feed( client, customer_id, campaign_id, feed_mapping, feed_resource_name, chain_id, ) end # Deletes the existing location extension feeds. def delete_location_extension_feeds(client, customer_id) # To delete a location extension feed, you need to # 1. Delete the customer feed so that the location extensions from the feed # stop serving. # 2. Delete the feed so that Google Ads will no longer sync from the Business # Profile account. customer_feeds = get_location_extension_customer_feeds(client, customer_id) unless customer_feeds.empty? # Optional: You may also want to delete the campaign and ad group feeds. remove_customer_feeds(client, customer_id, customer_feeds) end feeds = get_location_extension_feeds(client, customer_id) unless feeds.empty? remove_feeds(client, customer_id, customer_feeds) end end # Gets the existing location extension customer feeds. def get_location_extension_customer_feeds(client, customer_id) customer_feeds = [] # Creates the query. A location extension customer feed can be identified # by filtering for placeholder_types as LOCATION (location extension feeds) or # placeholder_types as AFFILIATE_LOCATION (affiliate location extension feeds). query = <<~QUERY SELECT customer_feed.resource_name FROM customer_feed WHERE customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION) AND customer_feed.status = ENABLED QUERY # Issues a search stream request. responses = client.service.google_ads.search_stream( customer_id: customer_id, query: query, ) # Iterates over all rows in all messages to collect the results. responses.each do |response| response.results.each do |row| customer_feeds << row.customer_feed end end customer_feeds end # Removes the customer feeds. def remove_customer_feeds(client, customer_id, customer_feeds) operations = [] customer_feeds.each do |customer_feed| operations << client.operation.remove_resource.customer_feed( customer_feed.resource_name) end # Issues a mutate request to remove the customer feeds. client.service.customer_feed.mutate_customer_feeds(customer_id, operations) end # Gets the existing location extension feeds. def get_location_extension_feeds(client, customer_id) feeds = [] # Creates the query. query = <<~QUERY SELECT feed.resource_name FROM feed WHERE feed.status = ENABLED AND feed.origin = USER QUERY # Issues a search stream request. responses = client.service.google_ads.search_stream( customer_id: customer_id, query: query, ) # Iterates over all rows in all messages to collect the results. responses.each do |response| response.results.each do |row| feeds << row.feed end end feeds end # Removes the feeds. def remove_feeds(client, customer_id, feeds) operations = [] feeds.each do |feed| operations << client.operation.remove_resource.feed(feed.resource_name) end # Issues a mutate request to remove the feeds. client.service.feed.mutate_feeds(customer_id, operations) end # Creates the affiliate location extension feed. def create_affiliate_location_extension_feed(client, customer_id, chain_id) # Creates a feed that will sync to retail addresses for a given retail # chain ID. # Do not add feed attributes, Google Ads will add them automatically because # this will be a system generated feed. operation = client.operation.create_resource.feed do |feed| feed.name = "Affiliate Location Extension feed ##{(Time.new.to_f * 1000).to_i}" feed.affiliate_location_feed_data = client.resource.affiliate_location_feed_data do |data| data.chain_ids << chain_id data.relationship_type = :GENERAL_RETAILER end # Since this feed's contents will be managed by Google, you must set its # origin to GOOGLE. feed.origin = :GOOGLE end # Issues a mutate request to add the feed and prints some information. response = client.service.feed.mutate_feeds( customer_id: customer_id, operations: [operation], ) feed_resource_name = response.results.first.resource_name puts "Affiliate location extension feed created with resource name: #{feed_resource_name}" feed_resource_name end # Waits for the affiliate location extension feed to be ready. An exponential # back-off policy with a maximum number of attempts is used to poll the server. def wait_for_feed_to_be_ready(client, customer_id, feed_resource_name) number_of_attempts = 0 while number_of_attempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS # Once you create a feed, Google's servers will setup the feed by creating # feed attributes and feed mapping. Once the feed mapping is created, it is # ready to be used for creating customer feed. # This process is asynchronous, so we wait until the feed mapping is # created, performing exponential backoff. feed_mapping = get_affiliated_location_extension_feed_mapping( client, customer_id, feed_resource_name) if feed_mapping.nil? number_of_attempts += 1 sleep_seconds = POLL_FREQUENCY_SECONDS * (2 ** number_of_attempts) puts "Checked #{number_of_attempts} time(s). Feed is not ready yet. " \ "Waiting #{sleep_seconds} seconds before trying again." sleep sleep_seconds else puts "Feed #{feed_resource_name} is now ready." return feed_mapping end end raise "The affiliate location feed mapping is still not ready after " \ "#{MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS} attempt(s)." end # Gets the affiliate location extension feed mapping. def get_affiliated_location_extension_feed_mapping( client, customer_id, feed_resource_name) # Creates a query that retrieves the feed mapping. query = <<~QUERY SELECT feed_mapping.resource_name, feed_mapping.attribute_field_mappings, feed_mapping.status FROM feed_mapping WHERE feed_mapping.feed = '#{feed_resource_name}' AND feed_mapping.status = ENABLED AND feed_mapping.placeholder_type = AFFILIATE_LOCATION LIMIT 1 QUERY # Issues a search request responses = client.service.google_ads.search( customer_id: customer_id, query: query, return_total_results_count: true, ) response = responses.page.response response.total_results_count == 1 ? response.results.first.feed_mapping : nil end # Creates the campaign feed. def create_campaign_feed( client, customer_id, campaign_id, feed_mapping, feed_resource_name, chain_id) matching_function = "IN(FeedAttribute[#{feed_resource_name.split('/')[3]}, " \ "#{get_attribute_id_for_chain_id(feed_mapping)}], #{chain_id})" # Adds a campaign feed that associates the feed with this campaign for the # AFFILIATE_LOCATION placeholder type. operation = client.operation.create_resource.campaign_feed do |cf| cf.feed = feed_resource_name cf.placeholder_types << :AFFILIATE_LOCATION cf.matching_function = client.resource.matching_function do |m| m.function_string = matching_function end cf.campaign = client.path.campaign(customer_id, campaign_id) end # Issues a mutate request to add the campaign feed and prints some information. response = client.service.campaign_feed.mutate_campaign_feeds( customer_id: customer_id, operations: [operation], ) puts "Campaign feed created with resource name: " \ "#{response.results.first.resource_name}" end # Gets the feed attribute ID for the retail chain ID. def get_attribute_id_for_chain_id(feed_mapping) feed_mapping.attribute_field_mappings.each do |fm| if fm.affiliate_location_field == :CHAIN_ID return fm.feed_attribute_id end end raise "Affiliate location feed mapping isn't setup correctly." end if __FILE__ == $0 POLL_FREQUENCY_SECONDS = 5 MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10 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[:chain_id] = 'INSERT_CHAIN_ID_HERE' options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' options[:should_delete_existing_feeds] = 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('-N', '--chain-id CHAIN-ID', String, 'Chain ID') do |v| options[:chain_id] = v end opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| options[:campaign_id] = v end opts.on('-D', '--should-delete-existing-feeds SHOULD-DELETE-EXISTING-FEEDS', TrueClass, 'Should delete existing feeds') do |v| options[:should_delete_existing_feeds] = v end opts.separator '' opts.separator 'Help:' opts.on_tail('-h', '--help', 'Show this message') do puts opts exit end end.parse! begin add_affiliate_location_extensions( options.fetch(:customer_id).tr("-", ""), options.fetch(:chain_id).to_i, options.fetch(:campaign_id), options[:should_delete_existing_feeds]) rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e e.failure.errors.each do |error| STDERR.printf("Error with message: %s\n", error.message) if error.location error.location.field_path_elements.each do |field_path_element| STDERR.printf("\tOn field: %s\n", field_path_element.field_name) end end error.error_code.to_h.each do |k, v| next if v == :UNSPECIFIED STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) end end raise end end
Perl
#!/usr/bin/perl -w # # Copyright 2020, 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 code example adds a feed that syncs retail addresses for a given retail # chain ID and associates the feed with a campaign for serving affiliate location # extensions. use strict; use warnings; use utf8; use FindBin qw($Bin); use lib "$Bin/../../lib"; use Google::Ads::GoogleAds::Client; use Google::Ads::GoogleAds::Utils::GoogleAdsHelper; use Google::Ads::GoogleAds::Utils::SearchStreamHandler; use Google::Ads::GoogleAds::V15::Resources::Feed; use Google::Ads::GoogleAds::V15::Resources::AffiliateLocationFeedData; use Google::Ads::GoogleAds::V15::Resources::CampaignFeed; use Google::Ads::GoogleAds::V15::Common::MatchingFunction; use Google::Ads::GoogleAds::V15::Enums::AffiliateLocationFeedRelationshipTypeEnum qw(GENERAL_RETAILER); use Google::Ads::GoogleAds::V15::Enums::FeedOriginEnum qw(GOOGLE); use Google::Ads::GoogleAds::V15::Enums::PlaceholderTypeEnum qw(AFFILIATE_LOCATION); use Google::Ads::GoogleAds::V15::Enums::AffiliateLocationPlaceholderFieldEnum qw(CHAIN_ID); use Google::Ads::GoogleAds::V15::Services::CustomerFeedService::CustomerFeedOperation; use Google::Ads::GoogleAds::V15::Services::FeedService::FeedOperation; use Google::Ads::GoogleAds::V15::Services::CampaignFeedService::CampaignFeedOperation; use Google::Ads::GoogleAds::V15::Utils::ResourceNames; use Getopt::Long qw(:config auto_help); use Pod::Usage; use Cwd qw(abs_path); use Data::Uniqid qw(uniqid); use Time::HiRes qw(sleep); # The maximum number of attempts to make to retrieve the feed mapping before # throwing an exception. use constant MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS => 10; # 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"; # The retail chain ID. For a complete list of valid retail chain IDs, see # https://developers.google.com/google-ads/api/reference/data/codes-formats#chain-ids. my $chain_id = "INSERT_CHAIN_ID_HERE"; # The campaign ID for which the affiliate location extensions are added. my $campaign_id = "INSERT_CAMPAIGN_ID_HERE"; # Optional: Delete all existing location extension feeds. This is required for # this code example to run correctly more than once. # 1. Google Ads only allows one location extension feed per email address. # 2. A Google Ads account cannot have a location extension feed and an affiliate # location extension feed at the same time. my $delete_existing_feeds = 0; sub add_affiliate_location_extensions { my ($api_client, $customer_id, $chain_id, $campaign_id, $delete_existing_feeds) = @_; if ($delete_existing_feeds) { delete_location_extension_feeds($api_client, $customer_id); } my $feed_resource_name = create_affiliate_location_extension_feed($api_client, $customer_id, $chain_id); # After the completion of the feed creation operation above the added feed # will not be available for usage in a campaign feed until the feed mapping # is created. # We will wait with an exponential back-off policy until the feed mapping has # been created. my $feed_mapping = wait_for_feed_to_be_ready($api_client, $customer_id, $feed_resource_name); create_campaign_feed($api_client, $customer_id, $campaign_id, $feed_mapping, $feed_resource_name, $chain_id); return 1; } # Deletes the existing location extension feeds. sub delete_location_extension_feeds { my ($api_client, $customer_id) = @_; # To delete a location extension feed, you need to # 1. Delete the customer feed so that the location extensions from the feed stop serving. # 2. Delete the feed so that Google Ads will no longer sync from the Business Profile account. my $customer_feeds = get_location_extension_customer_feeds($api_client, $customer_id); if (scalar @$customer_feeds > 0) { # Optional: You may also want to delete the campaign and ad group feeds. remove_customer_feeds($api_client, $customer_id, $customer_feeds); } my $feeds = get_location_extension_feeds($api_client, $customer_id); if (scalar @$feeds > 0) { remove_feeds($api_client, $customer_id, $feeds); } } # Gets the existing location extension customer feeds. sub get_location_extension_customer_feeds { my ($api_client, $customer_id) = @_; my $customer_feeds = []; my $google_ads_service = $api_client->GoogleAdsService(); # Create the query. A location extension customer feed can be identified by # filtering for placeholder_types as LOCATION (location extension feeds) or # placeholder_types as AFFILIATE_LOCATION (affiliate location extension feeds). my $search_query = "SELECT customer_feed.resource_name FROM customer_feed " . "WHERE customer_feed.placeholder_types CONTAINS " . "ANY(LOCATION, AFFILIATE_LOCATION) AND customer_feed.status = ENABLED"; my $search_stream_handler = Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({ service => $google_ads_service, request => { customerId => $customer_id, query => $search_query }}); # Issue a search stream request. $search_stream_handler->process_contents( sub { my $google_ads_row = shift; push @$customer_feeds, $google_ads_row->{customerFeed}; }); return $customer_feeds; } # Removes the customer feeds. sub remove_customer_feeds { my ($api_client, $customer_id, $customer_feeds) = @_; my $operations = []; foreach my $customer_feed (@$customer_feeds) { push @$operations, Google::Ads::GoogleAds::V15::Services::CustomerFeedService::CustomerFeedOperation ->new({remove => $customer_feed->{resourceName}}); } # Issue a mutate request to remove the customer feeds. $api_client->CustomerFeedService()->mutate({ customerId => $customer_id, operations => $operations }); } # Gets the existing location extension feeds. sub get_location_extension_feeds { my ($api_client, $customer_id) = @_; my $feeds = []; my $google_ads_service = $api_client->GoogleAdsService(); # Create the query. my $search_query = "SELECT feed.resource_name FROM feed " . "WHERE feed.status = ENABLED AND feed.origin = USER"; my $search_stream_handler = Google::Ads::GoogleAds::Utils::SearchStreamHandler->new({ service => $google_ads_service, request => { customerId => $customer_id, query => $search_query }}); # Issue a search stream request. $search_stream_handler->process_contents( sub { my $google_ads_row = shift; push @$feeds, $google_ads_row->{feed}; }); return $feeds; } # Removes the feeds. sub remove_feeds { my ($api_client, $customer_id, $feeds) = @_; my $operations = []; foreach my $feed (@$feeds) { push @$operations, Google::Ads::GoogleAds::V15::Services::FeedService::FeedOperation->new( {remove => $feed->{resourceName}}); } # Issue a mutate request to remove the feeds. $api_client->FeedService()->mutate({ customerId => $customer_id, operations => $operations }); } # Creates the affiliate location extension feed. sub create_affiliate_location_extension_feed { my ($api_client, $customer_id, $chain_id) = @_; # Create a feed that will sync to retail addresses for a given retail chain ID. # Do not add feed attributes, Google Ads will add them automatically because # this will be a system generated feed. my $feed = Google::Ads::GoogleAds::V15::Resources::Feed->new({ name => "Affiliate Location Extension feed #" . uniqid(), affiliateLocationFeedData => Google::Ads::GoogleAds::V15::Resources::AffiliateLocationFeedData->new({ chainIds => [$chain_id], relationshipType => GENERAL_RETAILER } ), # Since this feed's contents will be managed by Google, you must set its # origin to GOOGLE. origin => GOOGLE }); # Create the feed operation. my $operation = Google::Ads::GoogleAds::V15::Services::FeedService::FeedOperation->new({ create => $feed }); # Issue a mutate request to add the feed and print some information. my $response = $api_client->FeedService()->mutate({ customerId => $customer_id, operations => [$operation]}); my $feed_resource_name = $response->{results}[0]{resourceName}; printf "Affiliate location extension feed created with resource name: '%s'.\n", $feed_resource_name; return $feed_resource_name; } # Waits for the affiliate location extension feed to be ready. sub wait_for_feed_to_be_ready { my ($api_client, $customer_id, $feed_resource_name) = @_; my $num_attempts = 0; while ($num_attempts < MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS) { # Once you create a feed, Google's servers will setup the feed by creating # feed attributes and feed mapping. Once the feed mapping is created, it is # ready to be used for creating customer feed. # This process is asynchronous, so we wait until the feed mapping is created, # performing exponential backoff. my $feed_mapping = get_affiliate_location_extension_feed_mapping($api_client, $customer_id, $feed_resource_name); if (!$feed_mapping) { $num_attempts++; my $sleep_seconds = 5 * (2**$num_attempts); printf "Checked: %d time(s). Feed is not ready yet. " . "Waiting %d seconds before trying again.\n", $num_attempts, $sleep_seconds; sleep($sleep_seconds); } else { printf "Feed '%s' is now ready.\n", $feed_resource_name; return $feed_mapping; } } die( sprintf "The affiliate location feed mapping is still not ready " . "after %d attempt(s).\n", MAX_FEED_MAPPING_RETRIEVAL_ATTEMPTS ); } # Gets the affiliate location extension feed mapping. sub get_affiliate_location_extension_feed_mapping { my ($api_client, $customer_id, $feed_resource_name) = @_; # Create a query that retrieves the feed mapping. my $search_query = "SELECT feed_mapping.resource_name, " . "feed_mapping.attribute_field_mappings, " . "feed_mapping.status FROM feed_mapping " . "WHERE feed_mapping.feed = '$feed_resource_name' " . "AND feed_mapping.status = ENABLED " . "AND feed_mapping.placeholder_type = AFFILIATE_LOCATION LIMIT 1"; # Issue a search request. my $response = $api_client->GoogleAdsService()->search({ customerId => $customer_id, query => $search_query, returnTotalResultsCount => "true" }); return $response->{totalResultsCount} && $response->{totalResultsCount} == 1 ? $response->{results}[0]{feedMapping} : undef; } # Create the campaign feed. sub create_campaign_feed { my ($api_client, $customer_id, $campaign_id, $feed_mapping, $feed_resource_name, $chain_id) = @_; my $feed_id = $1 if $feed_resource_name =~ /(\d+)$/; my $attribute_id_for_chain_id = get_attribute_id_for_chain_id($feed_mapping); my $matching_function = "IN(FeedAttribute[$feed_id, $attribute_id_for_chain_id], $chain_id)"; # Add a campaign feed that associates the feed with this campaign for the # AFFILIATE_LOCATION placeholder type. my $campaign_feed = Google::Ads::GoogleAds::V15::Resources::CampaignFeed->new( { feed => $feed_resource_name, placeholderTypes => AFFILIATE_LOCATION, matchingFunction => Google::Ads::GoogleAds::V15::Common::MatchingFunction->new({ functionString => $matching_function } ), campaign => Google::Ads::GoogleAds::V15::Utils::ResourceNames::campaign( $customer_id, $campaign_id )}); # Create the campaign feed operation. my $operation = Google::Ads::GoogleAds::V15::Services::CampaignFeedService::CampaignFeedOperation ->new({ create => $campaign_feed }); # Issue a mutate request to add the campaign feed and print some information. my $response = $api_client->CampaignFeedService()->mutate({ customerId => $customer_id, operations => [$operation]}); printf "Campaign feed created with resource name: '%s'.\n", $response->{results}[0]{resourceName}; } # Gets the feed attribute ID for the retail chain ID. sub get_attribute_id_for_chain_id { my ($feed_mapping) = @_; foreach my $field_mapping (@{$feed_mapping->{attributeFieldMappings}}) { if ($field_mapping->{affiliateLocationField} eq CHAIN_ID) { return $field_mapping->{feedAttributeId}; } } die "Affiliate location feed mapping isn't setup correctly."; } # 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, "chain_id=i" => \$chain_id, "campaign_id=i" => \$campaign_id, "delete_existing_feeds=i" => \$delete_existing_feeds, ); # 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, $chain_id, $campaign_id); # Call the example. add_affiliate_location_extensions($api_client, $customer_id =~ s/-//gr, $chain_id, $campaign_id, $delete_existing_feeds); =pod =head1 NAME add_affiliate_location_extensions =head1 DESCRIPTION This code example adds a feed that syncs retail addresses for a given retail chain ID and associates the feed with a campaign for serving affiliate location extensions. =head1 SYNOPSIS add_affiliate_location_extensions.pl [options] -help Show the help message. -customer_id The Google Ads customer ID. -chain_id The retail chain ID. -campaign_id The campaign ID for which the affiliate location extensions are added. -delete_existing_feeds [optional] Non-zero if it should delete the existing feeds. =cut