Add Performance Max For Travel Goals Campaign

  • The code demonstrates creating a Performance Max campaign for travel goals using the Google Ads API.

  • It utilizes the TravelAssetSuggestionService to get suggested assets and creates additional assets if needed to meet minimum requirements.

  • The campaign budget, campaign, asset group, and assets are created in a single mutate request for atomicity.

  • The example shows how to create and link various asset types including text, image, hotel property, and call-to-action assets.

Java

 // Copyright 2023 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.travel 
 ; 
 import static 
  
 com.google.ads.googleads.examples.utils.CodeSampleHelper.getPrintableDateTime 
 ; 
 import static 
  
 com.google.ads.googleads.v23.enums.EuPoliticalAdvertisingStatusEnum.EuPoliticalAdvertisingStatus.DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING 
 ; 
 import 
  
 com.beust.jcommander.Parameter 
 ; 
 import 
  
 com.google.ads.googleads.examples.utils.ArgumentNames 
 ; 
 import 
  
 com.google.ads.googleads.examples.utils.CodeSampleHelper 
 ; 
 import 
  
 com.google.ads.googleads.examples.utils.CodeSampleParams 
 ; 
 import 
  
 com.google.ads.googleads.lib.GoogleAdsClient 
 ; 
 import 
  
 com.google.ads.googleads.v23.common.CallToActionAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.common.HotelPropertyAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.common.ImageAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.common.MaximizeConversionValue 
 ; 
 import 
  
 com.google.ads.googleads.v23.common.TextAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.AdvertisingChannelTypeEnum.AdvertisingChannelType 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.AssetFieldTypeEnum.AssetFieldType 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.AssetGroupStatusEnum.AssetGroupStatus 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.AssetSetTypeEnum.AssetSetType 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.BudgetDeliveryMethodEnum.BudgetDeliveryMethod 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.CampaignStatusEnum.CampaignStatus 
 ; 
 import 
  
 com.google.ads.googleads.v23.enums.HotelAssetSuggestionStatusEnum.HotelAssetSuggestionStatus 
 ; 
 import 
  
 com.google.ads.googleads.v23.errors.GoogleAdsError 
 ; 
 import 
  
 com.google.ads.googleads.v23.errors.GoogleAdsException 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.Asset 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.AssetGroup 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.AssetGroupAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.AssetSet 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.AssetSetAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.Campaign 
 ; 
 import 
  
 com.google.ads.googleads.v23.resources.CampaignBudget 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.AssetGroupAssetOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.AssetGroupOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.AssetOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.AssetSetAssetOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.AssetSetOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.AssetSetServiceClient 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.CampaignBudgetOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.CampaignOperation 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.GoogleAdsServiceClient 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.HotelAssetSuggestion 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.HotelImageAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.HotelTextAsset 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.MutateAssetSetsResponse 
 ; 
 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.SuggestTravelAssetsRequest 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.SuggestTravelAssetsResponse 
 ; 
 import 
  
 com.google.ads.googleads.v23.services.TravelAssetSuggestionServiceClient 
 ; 
 import 
  
 com.google.ads.googleads.v23.utils.ResourceNames 
 ; 
 import 
  
 com.google.common.collect.ImmutableList 
 ; 
 import 
  
 com.google.common.collect.ImmutableMap 
 ; 
 import 
  
 com.google.common.io.ByteStreams 
 ; 
 import 
  
 com.google.protobuf.ByteString 
 ; 
 import 
  
 java.io.FileNotFoundException 
 ; 
 import 
  
 java.io.IOException 
 ; 
 import 
  
 java.net.URL 
 ; 
 import 
  
 java.util.ArrayList 
 ; 
 import 
  
 java.util.HashMap 
 ; 
 import 
  
 java.util.List 
 ; 
 import 
  
 java.util.Map 
 ; 
 import 
  
 java.util.Map.Entry 
 ; 
 import 
  
 java.util.stream.Collectors 
 ; 
 import 
  
 org.joda.time.DateTime 
 ; 
 /** 
 * This example shows how to create a Performance Max for travel goals campaign. It also uses 
 * TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case there 
 * are not enough assets for the asset group (required by Performance Max), this example will create 
 * more assets to fulfill the requirements. 
 * 
 * <p>For more information about Performance Max campaigns, see 
 * https://developers.google.com/google-ads/api/docs/performance-max/overview. 
 * 
 * <p>Prerequisites: 
 * 
 * <ul> 
 *   <li>You must have at least one conversion action in the account. For more about conversion 
 *       actions, see 
 *       https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
 * </ul> 
 * 
 * <p>Notes: 
 * 
 * <ul> 
 *   <li>This example uses the default customer conversion goals. For an example of setting 
 *       campaign-specific conversion goals, see {@link 
 *       com.google.ads.googleads.examples.shoppingads.AddPerformanceMaxRetailCampaign}. 
 *   <li>To learn how to create asset group signals, see {@link 
 *       com.google.ads.googleads.examples.advancedoperations.AddPerformanceMaxCampaign}. 
 * </ul> 
 */ 
 public 
  
 class 
 AddPerformanceMaxForTravelGoalsCampaign 
  
 { 
  
 // Minimum requirements of assets required in a Performance Max asset group. 
  
 // See https://developers.google.com/google-ads/api/docs/performance-max/assets for details. 
  
 private 
  
 static 
  
 Map<AssetFieldType 
 , 
  
 Integer 
>  
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
  
 = 
  
 ImmutableMap 
 . 
< AssetFieldType 
 , 
  
 Integer>builder 
 () 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 HEADLINE 
 , 
  
 3 
 ) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 LONG_HEADLINE 
 , 
  
 1 
 ) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 DESCRIPTION 
 , 
  
 2 
 ) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 BUSINESS_NAME 
 , 
  
 1 
 ) 
  
 . 
 build 
 (); 
  
 private 
  
 static 
  
 Map<AssetFieldType 
 , 
  
 Integer 
>  
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
  
 = 
  
 ImmutableMap 
 . 
< AssetFieldType 
 , 
  
 Integer>builder 
 () 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 MARKETING_IMAGE 
 , 
  
 1 
 ) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 SQUARE_MARKETING_IMAGE 
 , 
  
 1 
 ) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 LOGO 
 , 
  
 1 
 ) 
  
 . 
 build 
 (); 
  
 // Texts and URLs used to create text and image assets when the TravelAssetSuggestionService 
  
 // doesn't return enough assets required for creating an asset group. 
  
 private 
  
 static 
  
 Map<AssetFieldType 
 , 
  
 List<String> 
>  
 DEFAULT_TEXT_ASSETS_INFO 
  
 = 
  
 ImmutableMap 
 . 
< AssetFieldType 
 , 
  
 List<String>>builder 
 () 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 HEADLINE 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "Hotel" 
 , 
  
 "Travel Reviews" 
 , 
  
 "Book travel" 
 )) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 LONG_HEADLINE 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "Travel the World" 
 )) 
  
 . 
 put 
 ( 
  
 AssetFieldType 
 . 
 DESCRIPTION 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "Great deal for your beloved hotel" 
 , 
  
 "Best rate guaranteed" 
 )) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 BUSINESS_NAME 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "Interplanetary Cruises" 
 )) 
  
 . 
 build 
 (); 
  
 private 
  
 static 
  
 Map<AssetFieldType 
 , 
  
 List<String> 
>  
 DEFAULT_IMAGE_ASSETS_INFO 
  
 = 
  
 ImmutableMap 
 . 
< AssetFieldType 
 , 
  
 List<String>>builder 
 () 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 MARKETING_IMAGE 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "https://gaagl.page.link/Eit5" 
 )) 
  
 . 
 put 
 ( 
  
 AssetFieldType 
 . 
 SQUARE_MARKETING_IMAGE 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "https://gaagl.page.link/bjYi" 
 )) 
  
 . 
 put 
 ( 
 AssetFieldType 
 . 
 LOGO 
 , 
  
 ImmutableList 
 . 
 of 
 ( 
 "https://gaagl.page.link/bjYi" 
 )) 
  
 . 
 build 
 (); 
  
 // We specify temporary IDs that are specific to a single mutate request. Temporary IDs are always 
  
 // negative and unique within one mutate request. 
  
 // 
  
 // <p>See https://developers.google.com/google-ads/api/docs/mutating/best-practices for further 
  
 // details. 
  
 // 
  
 // <p>These temporary IDs are fixed because they are used in multiple places. 
  
 private 
  
 static 
  
 final 
  
 int 
  
 ASSET_TEMPORARY_ID 
  
 = 
  
 - 
 1 
 ; 
  
 private 
  
 static 
  
 final 
  
 int 
  
 BUDGET_TEMPORARY_ID 
  
 = 
  
 - 
 2 
 ; 
  
 private 
  
 static 
  
 final 
  
 int 
  
 CAMPAIGN_TEMPORARY_ID 
  
 = 
  
 - 
 3 
 ; 
  
 private 
  
 static 
  
 final 
  
 int 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 = 
  
 - 
 4 
 ; 
  
 // There are also entities that will be created in the same request but do not 
  
 // need to be fixed temporary IDs because they are referenced only once. 
  
 private 
  
 static 
  
 long 
  
 temporaryId 
  
 = 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 - 
  
 1 
 ; 
  
 /** Parameters for the example. */ 
  
 private 
  
 static 
  
 class 
 AddPerformanceMaxForTravelGoalsCampaignParams 
  
 extends 
  
 CodeSampleParams 
  
 { 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 ArgumentNames 
 . 
 CUSTOMER_ID 
 , 
  
 required 
  
 = 
  
 true 
 ) 
  
 private 
  
 Long 
  
 customerId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 ArgumentNames 
 . 
 PLACE_ID 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "The place ID of a hotel property. A place ID uniquely identifies a place in the Google" 
  
 + 
  
 " Places database. See https://developers.google.com/places/web-service/place-id" 
  
 + 
  
 " to learn more." 
 ) 
  
 private 
  
 String 
  
 placeId 
 ; 
  
 } 
  
 public 
  
 static 
  
 void 
  
 main 
 ( 
 String 
 [] 
  
 args 
 ) 
  
 { 
  
 AddPerformanceMaxForTravelGoalsCampaignParams 
  
 params 
  
 = 
  
 new 
  
 AddPerformanceMaxForTravelGoalsCampaignParams 
 (); 
  
 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 
 . 
 placeId 
  
 = 
  
 "INSERT_PLACE_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 
  
 AddPerformanceMaxForTravelGoalsCampaign 
 () 
  
 . 
 runExample 
 ( 
 googleAdsClient 
 , 
  
 params 
 . 
 customerId 
 , 
  
 params 
 . 
 placeId 
 ); 
  
 } 
  
 catch 
  
 ( 
 GoogleAdsException 
  
 gae 
 ) 
  
 { 
  
 // GoogleAdsException is the base class for most exceptions thrown by an API request. 
  
 // Instances of this exception have a message and a GoogleAdsFailure that contains a 
  
 // collection of GoogleAdsErrors that indicate the underlying causes of the 
  
 // GoogleAdsException. 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
  
 "Request ID %s failed due to GoogleAdsException. Underlying errors:%n" 
 , 
  
 gae 
 . 
 getRequestId 
 ()); 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 for 
  
 ( 
 GoogleAdsError 
  
 googleAdsError 
  
 : 
  
 gae 
 . 
 getGoogleAdsFailure 
 (). 
 getErrorsList 
 ()) 
  
 { 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
 "  Error %d: %s%n" 
 , 
  
 i 
 ++ 
 , 
  
 googleAdsError 
 ); 
  
 } 
  
 System 
 . 
 exit 
 ( 
 1 
 ); 
  
 } 
  
 } 
  
 /** 
 * Runs the example. 
 * 
 * @param googleAdsClient the Google Ads API client. 
 * @param customerId the client customer ID. 
 * @param placeId the place ID for a hotel property asset. 
 */ 
  
 private 
  
 void 
  
 runExample 
 ( 
 GoogleAdsClient 
  
 googleAdsClient 
 , 
  
 long 
  
 customerId 
 , 
  
 String 
  
 placeId 
 ) 
  
 { 
  
 // Gets hotel asset suggestion using the TravelAssetSuggestionService. 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
  
 = 
  
 getHotelAssetSuggestion 
 ( 
 googleAdsClient 
 , 
  
 customerId 
 , 
  
 placeId 
 ); 
  
 // Performance Max campaigns require that repeated assets such as headlines and descriptions be 
  
 // created before the campaign. For the list of required assets for a Performance Max campaign, 
  
 // see 
  
 // https://developers.google.com/google-ads/api/docs/performance-max/assets. 
  
 // 
  
 // This step is the same for any type of Performance Max campaign. 
  
 // Creates the headlines using the hotel asset suggestion. 
  
 List<String> 
  
 headlineAssetResourceNames 
  
 = 
  
 createMultipleTextAssets 
 ( 
  
 googleAdsClient 
 , 
  
 customerId 
 , 
  
 AssetFieldType 
 . 
 HEADLINE 
 , 
  
 hotelAssetSuggestion 
 ); 
  
 // Creates the descriptions using the hotel asset suggestion. 
  
 List<String> 
  
 descriptionAssetResourceNames 
  
 = 
  
 createMultipleTextAssets 
 ( 
  
 googleAdsClient 
 , 
  
 customerId 
 , 
  
 AssetFieldType 
 . 
 DESCRIPTION 
 , 
  
 hotelAssetSuggestion 
 ); 
  
 // Creates a hotel property asset set, which will be used later to link with a newly created 
  
 // campaign. 
  
 String 
  
 hotelPropertyAssetSetResourceName 
  
 = 
  
 createHotelAssetSet 
 ( 
 googleAdsClient 
 , 
  
 customerId 
 ); 
  
 // Creates a hotel property asset and link it with the previously created hotel property asset 
  
 // set. This asset will also be linked to an asset group in the later steps. In the real-world 
  
 // scenario, you'd need to create many assets for all your hotel properties. We use one hotel 
  
 // property here for simplicity. Both asset and asset set need to be created before creating a 
  
 // campaign, so we cannot bundle them with other mutate operations below. 
  
 String 
  
 hotelPropertyAssetResourceName 
  
 = 
  
 createHotelAsset 
 ( 
 googleAdsClient 
 , 
  
 customerId 
 , 
  
 placeId 
 , 
  
 hotelPropertyAssetSetResourceName 
 ); 
  
 // It's important to create the below entities in this order because they depend on 
  
 // each other. 
  
 // The below methods create and return mutate operations that we later provide to the 
  
 // GoogleAdsService.Mutate method in order to create the entities in a single request. 
  
 // Since the entities for a Performance Max campaign are closely tied to one-another, it's 
  
 // considered a best practice to create them in a single Mutate request so they all complete 
  
 // successfully or fail entirely, leaving no orphaned entities. See: 
  
 // https://developers.google.com/google-ads/api/docs/mutating/overview. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 mutateOperations 
 . 
 add 
 ( 
 createCampaignBudgetOperation 
 ( 
 customerId 
 )); 
  
 mutateOperations 
 . 
 add 
 ( 
 createCampaignOperation 
 ( 
 customerId 
 , 
  
 hotelPropertyAssetSetResourceName 
 )); 
  
 mutateOperations 
 . 
 addAll 
 ( 
  
 createAssetGroupOperations 
 ( 
  
 customerId 
 , 
  
 hotelPropertyAssetResourceName 
 , 
  
 headlineAssetResourceNames 
 , 
  
 descriptionAssetResourceNames 
 , 
  
 hotelAssetSuggestion 
 )); 
  
 // Issues a mutate request to create everything and prints the results. 
  
 try 
  
 ( 
 GoogleAdsServiceClient 
  
 googleAdsServiceClient 
  
 = 
  
 googleAdsClient 
 . 
 getLatestVersion 
 (). 
 createGoogleAdsServiceClient 
 ()) 
  
 { 
  
 MutateGoogleAdsResponse 
  
 response 
  
 = 
  
 googleAdsServiceClient 
 . 
 mutate 
 ( 
 Long 
 . 
 toString 
 ( 
 customerId 
 ), 
  
 mutateOperations 
 ); 
  
 System 
 . 
 out 
 . 
 println 
 ( 
  
 "Created the following entities for a campaign budget, a campaign, and an asset group for" 
  
 + 
  
 " Performance Max for travel goals:" 
 ); 
  
 printResponseDetails 
 ( 
 response 
 ); 
  
 } 
  
 } 
  
 /** 
 * Returns hotel asset suggestion obtained from TravelAssetsSuggestionService. 
 * 
 * @param googleAdsClient the Google Ads API client. 
 * @param customerId the client customer ID. 
 * @param placeId the place ID for a hotel property asset. 
 * @return a hotel asset suggestion. 
 */ 
  
 private 
  
 HotelAssetSuggestion 
  
 getHotelAssetSuggestion 
 ( 
  
 GoogleAdsClient 
  
 googleAdsClient 
 , 
  
 long 
  
 customerId 
 , 
  
 String 
  
 placeId 
 ) 
  
 { 
  
 try 
  
 ( 
 TravelAssetSuggestionServiceClient 
  
 travelAssetSuggestionServiceClient 
  
 = 
  
 googleAdsClient 
 . 
 getLatestVersion 
 (). 
 createTravelAssetSuggestionServiceClient 
 ()) 
  
 { 
  
 // Sends a request to suggest assets to be created as an asset group for the Performance Max 
  
 // for travel goals campaign. 
  
 SuggestTravelAssetsResponse 
  
 suggestTravelAssetsResponse 
  
 = 
  
 travelAssetSuggestionServiceClient 
 . 
 suggestTravelAssets 
 ( 
  
 SuggestTravelAssetsRequest 
 . 
 newBuilder 
 () 
  
 . 
 setCustomerId 
 ( 
 Long 
 . 
 toString 
 ( 
 customerId 
 )) 
  
 // Uses 'en-US' as an example. It can be any language specifications in BCP 47 
  
 // format. 
  
 . 
 setLanguageOption 
 ( 
 "en-US" 
 ) 
  
 // The service accepts several place IDs. We use only one here for demonstration. 
  
 . 
 addPlaceIds 
 ( 
 placeId 
 ) 
  
 . 
 build 
 ()); 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Fetched a hotel asset suggestion for the place ID '%s'.%n" 
 , 
  
 placeId 
 ); 
  
 return 
  
 suggestTravelAssetsResponse 
 . 
 getHotelAssetSuggestions 
 ( 
 0 
 ); 
  
 } 
  
 } 
  
 /** 
 * Creates multiple text assets and returns the list of resource names. The hotel asset suggestion 
 * is used to create a text asset first. If the number of created text assets is still fewer than 
 * the minimum required number of assets of the specified asset field type, adds more text assets 
 * to fulfill the requirement. 
 * 
 * @param googleAdsClient the Google Ads API client. 
 * @param customerId the client customer ID. 
 * @param assetFieldType the asset field type that the text assets will be created for. 
 * @param hotelAssetSuggestion the hotel asset suggestion. 
 * @return the resource names of the created text assets. 
 */ 
  
 private 
  
 List<String> 
  
 createMultipleTextAssets 
 ( 
  
 GoogleAdsClient 
  
 googleAdsClient 
 , 
  
 long 
  
 customerId 
 , 
  
 AssetFieldType 
  
 assetFieldType 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 ) 
  
 { 
  
 // Uses the GoogleAdService to create multiple text assets in a single request. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 // First, adds all the text assets of the specified asset field type. 
  
 // Filters to only the specified asset field type. 
  
 // Constructs a mutate operation to create the asset. 
  
 // Adds the operation to the list. 
  
 if 
  
 ( 
 HotelAssetSuggestionStatus 
 . 
 SUCCESS 
 . 
 equals 
 ( 
 hotelAssetSuggestion 
 . 
 getStatus 
 ())) 
  
 { 
  
 for 
  
 ( 
 HotelTextAsset 
  
 asset 
  
 : 
  
 hotelAssetSuggestion 
 . 
 getTextAssetsList 
 ()) 
  
 { 
  
 if 
  
 ( 
 asset 
 . 
 getAssetFieldType 
 (). 
 equals 
 ( 
 assetFieldType 
 )) 
  
 { 
  
 MutateOperation 
  
 build 
  
 = 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetOperation 
 ( 
  
 AssetOperation 
 . 
 newBuilder 
 () 
  
 . 
 setCreate 
 ( 
  
 Asset 
 . 
 newBuilder 
 () 
  
 . 
 setTextAsset 
 ( 
  
 TextAsset 
 . 
 newBuilder 
 (). 
 setText 
 ( 
 asset 
 . 
 getText 
 ()). 
 build 
 ()) 
  
 . 
 build 
 ())) 
  
 . 
 build 
 (); 
  
 mutateOperations 
 . 
 add 
 ( 
 build 
 ); 
  
 } 
  
 } 
  
 } 
  
 // If the added assets are still less than the minimum required assets for the asset field type, 
  
 // add more text assets using the default texts. 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 while 
  
 ( 
 mutateOperations 
 . 
 size 
 () 
 < 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 . 
 get 
 ( 
 assetFieldType 
 )) 
  
 { 
  
 String 
  
 text 
  
 = 
  
 DEFAULT_TEXT_ASSETS_INFO 
 . 
 get 
 ( 
 assetFieldType 
 ). 
 get 
 ( 
 i 
 ++ 
 ); 
  
 // Creates a mutate operation for a text asset, using the default text. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetOperation 
 ( 
  
 AssetOperation 
 . 
 newBuilder 
 () 
  
 . 
 setCreate 
 ( 
  
 Asset 
 . 
 newBuilder 
 () 
  
 . 
 setTextAsset 
 ( 
 TextAsset 
 . 
 newBuilder 
 (). 
 setText 
 ( 
 text 
 ). 
 build 
 ()) 
  
 . 
 build 
 ())) 
  
 . 
 build 
 ()); 
  
 } 
  
 // Issues a mutate request to add all assets. 
  
 List<String> 
  
 assetResourceNames 
 ; 
  
 try 
  
 ( 
 GoogleAdsServiceClient 
  
 googleAdsServiceClient 
  
 = 
  
 googleAdsClient 
 . 
 getLatestVersion 
 (). 
 createGoogleAdsServiceClient 
 ()) 
  
 { 
  
 MutateGoogleAdsResponse 
  
 response 
  
 = 
  
 googleAdsServiceClient 
 . 
 mutate 
 ( 
 Long 
 . 
 toString 
 ( 
 customerId 
 ), 
  
 mutateOperations 
 ); 
  
 assetResourceNames 
  
 = 
  
 response 
 . 
 getMutateOperationResponsesList 
 (). 
 stream 
 () 
  
 . 
 map 
 ( 
 resp 
  
 - 
>  
 resp 
 . 
 getAssetResult 
 (). 
 getResourceName 
 ()) 
  
 . 
 collect 
 ( 
 Collectors 
 . 
 toList 
 ()); 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "The following assets were created for the asset field type '%s':%n" 
 , 
  
 assetFieldType 
 ); 
  
 printResponseDetails 
 ( 
 response 
 ); 
  
 } 
  
 return 
  
 assetResourceNames 
 ; 
  
 } 
  
 /** 
 * Creates a hotel property asset set. 
 * 
 * @param googleAdsClient the Google Ads API client. 
 * @param customerId the client customer ID. 
 * @return the created hotel property asset set resource name. 
 */ 
  
 private 
  
 String 
  
 createHotelAssetSet 
 ( 
 GoogleAdsClient 
  
 googleAdsClient 
 , 
  
 long 
  
 customerId 
 ) 
  
 { 
  
 // Creates an asset set operation for a hotel property asset set. 
  
 AssetSetOperation 
  
 assetSetOperation 
  
 = 
  
 AssetSetOperation 
 . 
 newBuilder 
 () 
  
 . 
 setCreate 
 ( 
  
 AssetSet 
 . 
 newBuilder 
 () 
  
 . 
 setName 
 ( 
  
 "My Hotel propery asset set #" 
  
 + 
  
 CodeSampleHelper 
 . 
 getPrintableDateTime 
 ()) 
  
 . 
 setType 
 ( 
 AssetSetType 
 . 
 HOTEL_PROPERTY 
 )) 
  
 . 
 build 
 (); 
  
 try 
  
 ( 
 AssetSetServiceClient 
  
 assetSetServiceClient 
  
 = 
  
 googleAdsClient 
 . 
 getLatestVersion 
 (). 
 createAssetSetServiceClient 
 ()) 
  
 { 
  
 MutateAssetSetsResponse 
  
 mutateAssetSetsResponse 
  
 = 
  
 assetSetServiceClient 
 . 
 mutateAssetSets 
 ( 
  
 Long 
 . 
 toString 
 ( 
 customerId 
 ), 
  
 ImmutableList 
 . 
 of 
 ( 
 assetSetOperation 
 )); 
  
 String 
  
 assetSetResourceName 
  
 = 
  
 mutateAssetSetsResponse 
 . 
 getResults 
 ( 
 0 
 ). 
 getResourceName 
 (); 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Created an asset set with resource name: '%s'%n" 
 , 
  
 assetSetResourceName 
 ); 
  
 return 
  
 assetSetResourceName 
 ; 
  
 } 
  
 } 
  
 /** 
 * Creates a hotel property asset using the specified place ID. The place ID must belong to a 
 * hotel property. Then, links it to the specified asset set. 
 * 
 * <p>See https://developers.google.com/places/web-service/place-id to search for a hotel place 
 * ID. 
 * 
 * @param googleAdsClient the Google Ads API client. 
 * @param customerId the client customer ID. 
 * @param placeId the place ID for a hotel. 
 * @param hotelPropertyAssetSetResourceName the hotel asset set resource name. 
 * @return the created hotel property asset resource name. 
 */ 
  
 private 
  
 String 
  
 createHotelAsset 
 ( 
  
 GoogleAdsClient 
  
 googleAdsClient 
 , 
  
 long 
  
 customerId 
 , 
  
 String 
  
 placeId 
 , 
  
 String 
  
 hotelPropertyAssetSetResourceName 
 ) 
  
 { 
  
 // Uses the GoogleAdService to create an asset and asset set asset in a single request. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 String 
  
 assetResourceName 
  
 = 
  
 ResourceNames 
 . 
 asset 
 ( 
 customerId 
 , 
  
 ASSET_TEMPORARY_ID 
 ); 
  
 // Creates a mutate operation for a hotel property asset. 
  
 Asset 
  
 hotelPropertyAsset 
  
 = 
  
 Asset 
 . 
 newBuilder 
 () 
  
 . 
 setResourceName 
 ( 
 assetResourceName 
 ) 
  
 . 
 setHotelPropertyAsset 
 ( 
 HotelPropertyAsset 
 . 
 newBuilder 
 (). 
 setPlaceId 
 ( 
 placeId 
 )) 
  
 . 
 build 
 (); 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetOperation 
 ( 
 AssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 hotelPropertyAsset 
 )) 
  
 . 
 build 
 ()); 
  
 // Creates a mutate operation for an asset set asset. 
  
 AssetSetAsset 
  
 assetSetAsset 
  
 = 
  
 AssetSetAsset 
 . 
 newBuilder 
 () 
  
 . 
 setAsset 
 ( 
 assetResourceName 
 ) 
  
 . 
 setAssetSet 
 ( 
 hotelPropertyAssetSetResourceName 
 ) 
  
 . 
 build 
 (); 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetSetAssetOperation 
 ( 
 AssetSetAssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 assetSetAsset 
 )) 
  
 . 
 build 
 ()); 
  
 // Issues a mutate request to create all entities. 
  
 try 
  
 ( 
 GoogleAdsServiceClient 
  
 googleAdsServiceClient 
  
 = 
  
 googleAdsClient 
 . 
 getLatestVersion 
 (). 
 createGoogleAdsServiceClient 
 ()) 
  
 { 
  
 MutateGoogleAdsResponse 
  
 mutateGoogleAdsResponse 
  
 = 
  
 googleAdsServiceClient 
 . 
 mutate 
 ( 
 Long 
 . 
 toString 
 ( 
 customerId 
 ), 
  
 mutateOperations 
 ); 
  
 System 
 . 
 out 
 . 
 println 
 ( 
 "Created the following entities for the hotel asset:" 
 ); 
  
 printResponseDetails 
 ( 
 mutateGoogleAdsResponse 
 ); 
  
 // Returns the created asset resource name, which will be used later to create an asset 
  
 // group. Other resource names are not used later. 
  
 return 
  
 mutateGoogleAdsResponse 
  
 . 
 getMutateOperationResponses 
 ( 
 0 
 ) 
  
 . 
 getAssetResult 
 () 
  
 . 
 getResourceName 
 (); 
  
 } 
  
 } 
  
 /** 
 * Creates a mutate operation that creates a new campaign budget. 
 * 
 * <p>A temporary ID will be assigned to this campaign budget so that it can be referenced by 
 * other objects being created in the same mutate request. 
 * 
 * @param customerId the client customer ID. 
 * @return a mutate operation that creates a campaign budget. 
 */ 
  
 private 
  
 MutateOperation 
  
 createCampaignBudgetOperation 
 ( 
 long 
  
 customerId 
 ) 
  
 { 
  
 CampaignBudget 
  
 campaignBudget 
  
 = 
  
 CampaignBudget 
 . 
 newBuilder 
 () 
  
 . 
 setName 
 ( 
 "Performance Max for travel goals campaign budget #" 
  
 + 
  
 getPrintableDateTime 
 ()) 
  
 // The budget period already defaults to DAILY. 
  
 . 
 setAmountMicros 
 ( 
 50_000_000 
 ) 
  
 . 
 setDeliveryMethod 
 ( 
 BudgetDeliveryMethod 
 . 
 STANDARD 
 ) 
  
 // A Performance Max campaign cannot use a shared campaign budget. 
  
 . 
 setExplicitlyShared 
 ( 
 false 
 ) 
  
 // Sets a temporary ID in the budget's resource name, so it can be referenced 
  
 // by the campaign in later steps. 
  
 . 
 setResourceName 
 ( 
 ResourceNames 
 . 
 campaignBudget 
 ( 
 customerId 
 , 
  
 BUDGET_TEMPORARY_ID 
 )) 
  
 . 
 build 
 (); 
  
 return 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setCampaignBudgetOperation 
 ( 
  
 CampaignBudgetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 campaignBudget 
 ). 
 build 
 ()) 
  
 . 
 build 
 (); 
  
 } 
  
 /** 
 * Creates a mutate operation that creates a new Performance Max campaign. Links the specified 
 * hotel property asset set to this campaign. 
 * 
 * <p>A temporary ID will be assigned to this campaign so that it can be referenced by other 
 * objects being created in the same mutate request. 
 * 
 * @param customerId the client customer ID. 
 * @param hotelPropertyAssetSetResourceName 
 * @return the mutate operation that creates a campaign. 
 */ 
  
 private 
  
 MutateOperation 
  
 createCampaignOperation 
 ( 
  
 long 
  
 customerId 
 , 
  
 String 
  
 hotelPropertyAssetSetResourceName 
 ) 
  
 { 
  
 Campaign 
  
 performanceMaxCampaign 
  
 = 
  
 Campaign 
 . 
 newBuilder 
 () 
  
 . 
 setName 
 ( 
 "Performance Max for travel goals campaign #" 
  
 + 
  
 getPrintableDateTime 
 ()) 
  
 // Sets the campaign status as PAUSED. The campaign is the only entity in 
  
 // the mutate request that should have its status set. 
  
 . 
 setStatus 
 ( 
 CampaignStatus 
 . 
 PAUSED 
 ) 
  
 // All Performance Max campaigns have an advertising_channel_type of 
  
 // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. 
  
 . 
 setAdvertisingChannelType 
 ( 
 AdvertisingChannelType 
 . 
 PERFORMANCE_MAX 
 ) 
  
 // To create a Performance Max for travel goals campaign, you need to set 
  
 // `hotel_property_asset_set`. 
  
 . 
 setHotelPropertyAssetSet 
 ( 
 hotelPropertyAssetSetResourceName 
 ) 
  
 // Bidding strategy must be set directly on the campaign. 
  
 // Setting a portfolio bidding strategy by resource name is not supported. 
  
 // Max Conversion and Maximize Conversion Value are the only strategies 
  
 // supported for Performance Max campaigns. 
  
 // An optional ROAS (Return on Advertising Spend) can be set for 
  
 // maximize_conversion_value. The ROAS value must be specified as a ratio in 
  
 // the API. It is calculated by dividing "total value" by "total spend". 
  
 // For more information on Maximize Conversion Value, see the support 
  
 // article: http://support.google.com/google-ads/answer/7684216. 
  
 // A targetRoas of 3.5 corresponds to a 350% return on ad spend. 
  
 . 
 setMaximizeConversionValue 
 ( 
  
 MaximizeConversionValue 
 . 
 newBuilder 
 (). 
 setTargetRoas 
 ( 
 3.5 
 ). 
 build 
 ()) 
  
 // Assigns the resource name with a temporary ID. 
  
 . 
 setResourceName 
 ( 
 ResourceNames 
 . 
 campaign 
 ( 
 customerId 
 , 
  
 CAMPAIGN_TEMPORARY_ID 
 )) 
  
 // Sets the budget using the given budget resource name. 
  
 . 
 setCampaignBudget 
 ( 
 ResourceNames 
 . 
 campaignBudget 
 ( 
 customerId 
 , 
  
 BUDGET_TEMPORARY_ID 
 )) 
  
 // Declares whether this campaign serves political ads targeting the EU. 
  
 . 
 setContainsEuPoliticalAdvertising 
 ( 
 DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING 
 ) 
  
 // Optional fields. 
  
 . 
 setStartDateTime 
 ( 
 new 
  
 DateTime 
 (). 
 plusDays 
 ( 
 1 
 ). 
 toString 
 ( 
 "yyyy-MM-dd 00:00:00" 
 )) 
  
 . 
 setEndDateTime 
 ( 
 new 
  
 DateTime 
 (). 
 plusDays 
 ( 
 365 
 ). 
 toString 
 ( 
 "yyyy-MM-dd 23:59:59" 
 )) 
  
 . 
 build 
 (); 
  
 return 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setCampaignOperation 
 ( 
  
 CampaignOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 performanceMaxCampaign 
 ). 
 build 
 ()) 
  
 . 
 build 
 (); 
  
 } 
  
 /** 
 * Creates a list of mutate operations that create a new asset group, composed of suggested 
 * assets. In case the number of suggested assets is not enough for the requirements, creates more 
 * assets to meet the requirement. 
 * 
 * <p>For the list of required assets for a Performance Max campaign, see 
 * https://developers.google.com/google-ads/api/docs/performance-max/assets. 
 * 
 * @param hotelPropertyAssetResourceName the hotel property asset resource name that will be used 
 *     to create an asset group 
 * @param headlineAssetResourceNames a list of headline resource names 
 * @param descriptionAssetResourceNames a list of description resource names 
 * @param hotelAssetSuggestion the hotel asset suggestion 
 * @return a list of mutate operations that create the asset group 
 */ 
  
 private 
  
 List<MutateOperation> 
  
 createAssetGroupOperations 
 ( 
  
 long 
  
 customerId 
 , 
  
 String 
  
 hotelPropertyAssetResourceName 
 , 
  
 List<String> 
  
 headlineAssetResourceNames 
 , 
  
 List<String> 
  
 descriptionAssetResourceNames 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 ) 
  
 { 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 // Creates a new mutate operation that creates an asset group using suggested information when 
  
 // available. 
  
 String 
  
 assetGroupName 
 ; 
  
 List<String> 
  
 assetGroupFinalUrls 
 ; 
  
 if 
  
 ( 
 HotelAssetSuggestionStatus 
 . 
 SUCCESS 
 . 
 equals 
 ( 
 hotelAssetSuggestion 
 . 
 getStatus 
 ())) 
  
 { 
  
 assetGroupName 
  
 = 
  
 hotelAssetSuggestion 
 . 
 getHotelName 
 (); 
  
 assetGroupFinalUrls 
  
 = 
  
 ImmutableList 
 . 
 of 
 ( 
 hotelAssetSuggestion 
 . 
 getFinalUrl 
 ()); 
  
 } 
  
 else 
  
 { 
  
 assetGroupName 
  
 = 
  
 "Performance Max for travel goals asset group #" 
  
 + 
  
 getPrintableDateTime 
 (); 
  
 assetGroupFinalUrls 
  
 = 
  
 ImmutableList 
 . 
 of 
 ( 
 "https://www.example.com" 
 ); 
  
 } 
  
 String 
  
 assetGroupResourceName 
  
 = 
  
 ResourceNames 
 . 
 assetGroup 
 ( 
 customerId 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ); 
  
 AssetGroup 
  
 assetGroup 
  
 = 
  
 AssetGroup 
 . 
 newBuilder 
 () 
  
 . 
 setResourceName 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setName 
 ( 
 assetGroupName 
 ) 
  
 . 
 setCampaign 
 ( 
 ResourceNames 
 . 
 campaign 
 ( 
 customerId 
 , 
  
 CAMPAIGN_TEMPORARY_ID 
 )) 
  
 . 
 addAllFinalUrls 
 ( 
 assetGroupFinalUrls 
 ) 
  
 . 
 setStatus 
 ( 
 AssetGroupStatus 
 . 
 PAUSED 
 ) 
  
 . 
 build 
 (); 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetGroupOperation 
 ( 
 AssetGroupOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 assetGroup 
 )) 
  
 . 
 build 
 ()); 
  
 // An asset group is linked to an asset by creating a new asset group asset 
  
 // and providing: 
  
 // -  the resource name of the asset group 
  
 // -  the resource name of the asset 
  
 // -  the field_type of the asset in this asset group 
  
 // 
  
 // To learn more about asset groups, see 
  
 // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. 
  
 // Headline and description assets were created at the first step of this example. So, we 
  
 // just need to link them with the created asset group. 
  
 // Builds the AssetGroupAssets to link headline assets to the asset group. 
  
 List<AssetGroupAsset> 
  
 assetGroupAssets 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 assetGroupAssets 
 . 
 addAll 
 ( 
  
 headlineAssetResourceNames 
 . 
 stream 
 () 
  
 . 
 map 
 ( 
  
 headlineAssetResourceName 
  
 - 
>  
 AssetGroupAsset 
 . 
 newBuilder 
 () 
  
 . 
 setAsset 
 ( 
 headlineAssetResourceName 
 ) 
  
 . 
 setAssetGroup 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setFieldType 
 ( 
 AssetFieldType 
 . 
 HEADLINE 
 ) 
  
 . 
 build 
 ()) 
  
 . 
 collect 
 ( 
 Collectors 
 . 
 toList 
 ())); 
  
 // Builds the AssetGroupAssets to link description assets to the asset group. 
  
 assetGroupAssets 
 . 
 addAll 
 ( 
  
 descriptionAssetResourceNames 
 . 
 stream 
 () 
  
 . 
 map 
 ( 
  
 descriptionAssetResourceName 
  
 - 
>  
 AssetGroupAsset 
 . 
 newBuilder 
 () 
  
 . 
 setAsset 
 ( 
 descriptionAssetResourceName 
 ) 
  
 . 
 setAssetGroup 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setFieldType 
 ( 
 AssetFieldType 
 . 
 DESCRIPTION 
 ) 
  
 . 
 build 
 ()) 
  
 . 
 collect 
 ( 
 Collectors 
 . 
 toList 
 ())); 
  
 // Adds operations to create the AssetGroupAssets for headlines and descriptions. 
  
 mutateOperations 
 . 
 addAll 
 ( 
  
 assetGroupAssets 
 . 
 stream 
 () 
  
 . 
 map 
 ( 
  
 assetGroupAsset 
  
 - 
>  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetGroupAssetOperation 
 ( 
  
 AssetGroupAssetOperation 
 . 
 newBuilder 
 () 
  
 . 
 setCreate 
 ( 
 assetGroupAsset 
 ) 
  
 . 
 build 
 ()) 
  
 . 
 build 
 ()) 
  
 . 
 collect 
 ( 
 Collectors 
 . 
 toList 
 ())); 
  
 // Link the previously created hotel property asset to the asset group. In the real-world 
  
 // scenario, you'd need to do this step several times for each hotel property asset. 
  
 AssetGroupAsset 
  
 hotelProperyAssetGroupAsset 
  
 = 
  
 AssetGroupAsset 
 . 
 newBuilder 
 () 
  
 . 
 setAsset 
 ( 
 hotelPropertyAssetResourceName 
 ) 
  
 . 
 setAssetGroup 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setFieldType 
 ( 
 AssetFieldType 
 . 
 HOTEL_PROPERTY 
 ) 
  
 . 
 build 
 (); 
  
 // Adds an operation to link the hotel property asset to the asset group. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetGroupAssetOperation 
 ( 
  
 AssetGroupAssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 hotelProperyAssetGroupAsset 
 )) 
  
 . 
 build 
 ()); 
  
 // Creates the rest of the required text assets and links them to the asset group. 
  
 mutateOperations 
 . 
 addAll 
 ( 
  
 createOperationsForTextAssetsAndAssetGroupAssets 
 ( 
  
 customerId 
 , 
  
 hotelAssetSuggestion 
 , 
  
 assetGroupResourceName 
 )); 
  
 // Creates the image assets and links them to the asset group. Some optional image assets 
  
 // suggested by the TravelAssetSuggestionService might be created too. 
  
 mutateOperations 
 . 
 addAll 
 ( 
  
 createOperationsForImageAssetsAndAssetGroupAssets 
 ( 
  
 customerId 
 , 
  
 hotelAssetSuggestion 
 , 
  
 assetGroupResourceName 
 )); 
  
 if 
  
 ( 
 HotelAssetSuggestionStatus 
 . 
 SUCCESS 
 . 
 equals 
 ( 
 hotelAssetSuggestion 
 . 
 getStatus 
 ())) 
  
 { 
  
 // Creates a new mutate operation for a suggested call-to-action asset and links it 
  
 // to the asset group. 
  
 Asset 
  
 callToActionAsset 
  
 = 
  
 Asset 
 . 
 newBuilder 
 () 
  
 . 
 setResourceName 
 ( 
 ResourceNames 
 . 
 asset 
 ( 
 customerId 
 , 
  
 temporaryId 
 )) 
  
 . 
 setName 
 ( 
 "Suggested call-to-action asset #" 
  
 + 
  
 CodeSampleHelper 
 . 
 getPrintableDateTime 
 ()) 
  
 . 
 setCallToActionAsset 
 ( 
  
 CallToActionAsset 
 . 
 newBuilder 
 () 
  
 . 
 setCallToAction 
 ( 
 hotelAssetSuggestion 
 . 
 getCallToAction 
 ())) 
  
 . 
 build 
 (); 
  
 // Adds an operation to create the call-to-action asset. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetOperation 
 ( 
 AssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 callToActionAsset 
 )) 
  
 . 
 build 
 ()); 
  
 AssetGroupAsset 
  
 callToActionAssetGroupAsset 
  
 = 
  
 AssetGroupAsset 
 . 
 newBuilder 
 () 
  
 . 
 setAsset 
 ( 
 callToActionAsset 
 . 
 getResourceName 
 ()) 
  
 . 
 setAssetGroup 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setFieldType 
 ( 
 AssetFieldType 
 . 
 CALL_TO_ACTION_SELECTION 
 ) 
  
 . 
 build 
 (); 
  
 // Adds an operation to link the call-to-action asset to the asset group. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetGroupAssetOperation 
 ( 
  
 AssetGroupAssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 callToActionAssetGroupAsset 
 )) 
  
 . 
 build 
 ()); 
  
 temporaryId 
 -- 
 ; 
  
 } 
  
 return 
  
 mutateOperations 
 ; 
  
 } 
  
 /** 
 * Creates text assets required for an asset group using the suggested hotel text assets. It adds 
 * more text assets to fulfill the requirements if the suggested hotel text assets are not enough. 
 * 
 * @param customerId the client customer ID. 
 * @param hotelAssetSuggestion the hotel asset suggestion. 
 * @return a list of mutate operations that create text assets and asset group assets. 
 */ 
  
 private 
  
 List<MutateOperation> 
  
 createOperationsForTextAssetsAndAssetGroupAssets 
 ( 
  
 long 
  
 customerId 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 , 
  
 String 
  
 assetGroupResourceName 
 ) 
  
 { 
  
 // Creates mutate operations for the suggested text assets except for headlines and 
  
 // descriptions, which were created previously. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 // Creates a map of asset field type to list of text values to create. 
  
 Map<AssetFieldType 
 , 
  
 List<String> 
>  
 textByFieldType 
  
 = 
  
 new 
  
 HashMap 
<> (); 
  
 if 
  
 ( 
 HotelAssetSuggestionStatus 
 . 
 SUCCESS 
 . 
 equals 
 ( 
 hotelAssetSuggestion 
 . 
 getStatus 
 ())) 
  
 { 
  
 // Adds text values of suggested text assets. 
  
 for 
  
 ( 
 HotelTextAsset 
  
 hotelTextAsset 
  
 : 
  
 hotelAssetSuggestion 
 . 
 getTextAssetsList 
 ()) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 hotelTextAsset 
 . 
 getAssetFieldType 
 (); 
  
 if 
  
 ( 
 AssetFieldType 
 . 
 HEADLINE 
 . 
 equals 
 ( 
 assetFieldType 
 ) 
  
 || 
  
 AssetFieldType 
 . 
 DESCRIPTION 
 . 
 equals 
 ( 
 assetFieldType 
 )) 
  
 { 
  
 // Headlines and descriptions were already created at the first step of this code example. 
  
 continue 
 ; 
  
 } 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "A text asset with text '%s' is suggested for the asset field type '%s'.%n" 
 , 
  
 hotelTextAsset 
 . 
 getText 
 (), 
  
 assetFieldType 
 ); 
  
 textByFieldType 
  
 . 
 computeIfAbsent 
 ( 
 assetFieldType 
 , 
  
 ft 
  
 - 
>  
 new 
  
 ArrayList 
<> ()) 
  
 . 
 add 
 ( 
 hotelTextAsset 
 . 
 getText 
 ()); 
  
 } 
  
 } 
  
 // Collects more text values by field type to fulfill the requirements. 
  
 for 
  
 ( 
 Entry<AssetFieldType 
 , 
  
 Integer 
>  
 requiredEntry 
  
 : 
  
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 . 
 entrySet 
 ()) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 requiredEntry 
 . 
 getKey 
 (); 
  
 if 
  
 ( 
 AssetFieldType 
 . 
 HEADLINE 
 . 
 equals 
 ( 
 assetFieldType 
 ) 
  
 || 
  
 AssetFieldType 
 . 
 DESCRIPTION 
 . 
 equals 
 ( 
 assetFieldType 
 )) 
  
 { 
  
 // Headlines and descriptions were already created at the first step of this code example. 
  
 continue 
 ; 
  
 } 
  
 textByFieldType 
 . 
 computeIfAbsent 
 ( 
 assetFieldType 
 , 
  
 k 
  
 - 
>  
 new 
  
 ArrayList 
<> ()); 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 while 
  
 ( 
 textByFieldType 
 . 
 get 
 ( 
 assetFieldType 
 ). 
 size 
 () 
 < 
 requiredEntry 
 . 
 getValue 
 ()) 
  
 { 
  
 String 
  
 textFromDefaults 
  
 = 
  
 DEFAULT_TEXT_ASSETS_INFO 
 . 
 get 
 ( 
 assetFieldType 
 ). 
 get 
 ( 
 i 
 ++ 
 ); 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "A default text '%s' is used to create a text asset for the asset field type '%s'.%n" 
 , 
  
 textFromDefaults 
 , 
  
 assetFieldType 
 ); 
  
 textByFieldType 
  
 . 
 computeIfAbsent 
 ( 
 assetFieldType 
 , 
  
 ft 
  
 - 
>  
 new 
  
 ArrayList 
<> ()) 
  
 . 
 add 
 ( 
 textFromDefaults 
 ); 
  
 } 
  
 } 
  
 // Converts the list of text values by field type into AssetOperations and 
  
 // AssetGroupAssetOperations. 
  
 for 
  
 ( 
 Entry<AssetFieldType 
 , 
  
 List<String> 
>  
 fieldTypeEntry 
  
 : 
  
 textByFieldType 
 . 
 entrySet 
 ()) 
  
 { 
  
 for 
  
 ( 
 String 
  
 text 
  
 : 
  
 fieldTypeEntry 
 . 
 getValue 
 ()) 
  
 { 
  
 // Builds the Asset. 
  
 Asset 
  
 asset 
  
 = 
  
 Asset 
 . 
 newBuilder 
 () 
  
 . 
 setResourceName 
 ( 
 ResourceNames 
 . 
 asset 
 ( 
 customerId 
 , 
  
 temporaryId 
 -- 
 )) 
  
 . 
 setTextAsset 
 ( 
 TextAsset 
 . 
 newBuilder 
 (). 
 setText 
 ( 
 text 
 )) 
  
 . 
 build 
 (); 
  
 // Adds an operation to create the Asset. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetOperation 
 ( 
 AssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 asset 
 )) 
  
 . 
 build 
 ()); 
  
 // Builds the AssetGroupAsset. 
  
 AssetGroupAsset 
  
 assetGroupAsset 
  
 = 
  
 AssetGroupAsset 
 . 
 newBuilder 
 () 
  
 // References the Asset above by resource name. 
  
 . 
 setAsset 
 ( 
 asset 
 . 
 getResourceName 
 ()) 
  
 . 
 setAssetGroup 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setFieldType 
 ( 
 fieldTypeEntry 
 . 
 getKey 
 ()) 
  
 . 
 build 
 (); 
  
 // Adds an operation to link the Asset to the AssetGroup. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetGroupAssetOperation 
 ( 
  
 AssetGroupAssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 assetGroupAsset 
 )) 
  
 . 
 build 
 ()); 
  
 } 
  
 } 
  
 return 
  
 mutateOperations 
 ; 
  
 } 
  
 /** 
 * Creates image assets required for an asset group using the suggested hotel image assets. It 
 * adds more image assets to fulfill the requirements if the suggested hotel image assets are not 
 * enough. 
 * 
 * @param customerId the client customer ID. 
 * @param hotelAssetSuggestion the hotel asset suggestion. 
 * @return a list of mutate operations that create image assets and asset group assets. 
 */ 
  
 private 
  
 List<MutateOperation> 
  
 createOperationsForImageAssetsAndAssetGroupAssets 
 ( 
  
 long 
  
 customerId 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 , 
  
 String 
  
 assetGroupResourceName 
 ) 
  
 { 
  
 // Creates mutate operations for the suggested image assets. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 // Creates a map of asset field type to list of image URLs for which this method will create 
  
 // assets and asset group assets. 
  
 Map<AssetFieldType 
 , 
  
 List<String> 
>  
 imageUrlsByFieldType 
  
 = 
  
 new 
  
 HashMap 
<> (); 
  
 if 
  
 ( 
 HotelAssetSuggestionStatus 
 . 
 SUCCESS 
 . 
 equals 
 ( 
 hotelAssetSuggestion 
 . 
 getStatus 
 ())) 
  
 { 
  
 // Adds URLs of suggested image assets. 
  
 for 
  
 ( 
 HotelImageAsset 
  
 hotelImageAsset 
  
 : 
  
 hotelAssetSuggestion 
 . 
 getImageAssetsList 
 ()) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 hotelImageAsset 
 . 
 getAssetFieldType 
 (); 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "An image asset with URL '%s' is suggested for the asset field type '%s'.%n" 
 , 
  
 hotelImageAsset 
 . 
 getUri 
 (), 
  
 assetFieldType 
 ); 
  
 imageUrlsByFieldType 
  
 . 
 computeIfAbsent 
 ( 
 assetFieldType 
 , 
  
 ft 
  
 - 
>  
 new 
  
 ArrayList 
<> ()) 
  
 . 
 add 
 ( 
 hotelImageAsset 
 . 
 getUri 
 ()); 
  
 } 
  
 } 
  
 // Collects more image URLs by field type to fulfill the requirements. 
  
 for 
  
 ( 
 Entry<AssetFieldType 
 , 
  
 Integer 
>  
 requiredEntry 
  
 : 
  
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 . 
 entrySet 
 ()) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 requiredEntry 
 . 
 getKey 
 (); 
  
 imageUrlsByFieldType 
 . 
 computeIfAbsent 
 ( 
 assetFieldType 
 , 
  
 k 
  
 - 
>  
 new 
  
 ArrayList 
<> ()); 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 while 
  
 ( 
 imageUrlsByFieldType 
 . 
 get 
 ( 
 assetFieldType 
 ). 
 size 
 () 
 < 
 requiredEntry 
 . 
 getValue 
 ()) 
  
 { 
  
 String 
  
 imageUrlFromDefaults 
  
 = 
  
 DEFAULT_IMAGE_ASSETS_INFO 
 . 
 get 
 ( 
 assetFieldType 
 ). 
 get 
 ( 
 i 
 ++ 
 ); 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
  
 "A default image URL '%s' is used to create an image asset for the asset field type" 
  
 + 
  
 " '%s'.%n" 
 , 
  
 imageUrlFromDefaults 
 , 
  
 assetFieldType 
 ); 
  
 imageUrlsByFieldType 
  
 . 
 computeIfAbsent 
 ( 
 assetFieldType 
 , 
  
 ft 
  
 - 
>  
 new 
  
 ArrayList 
<> ()) 
  
 . 
 add 
 ( 
 imageUrlFromDefaults 
 ); 
  
 } 
  
 } 
  
 // Converts the list of URLs by field type into AssetOperations and AssetGroupAssetOperations. 
  
 for 
  
 ( 
 Entry<AssetFieldType 
 , 
  
 List<String> 
>  
 fieldTypeEntry 
  
 : 
  
 imageUrlsByFieldType 
 . 
 entrySet 
 ()) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 fieldTypeEntry 
 . 
 getKey 
 (); 
  
 for 
  
 ( 
 String 
  
 url 
  
 : 
  
 fieldTypeEntry 
 . 
 getValue 
 ()) 
  
 { 
  
 // Retrieves the image data from the URL. 
  
 byte 
 [] 
  
 imageData 
 ; 
  
 try 
  
 { 
  
 imageData 
  
 = 
  
 ByteStreams 
 . 
 toByteArray 
 ( 
 new 
  
 URL 
 ( 
 url 
 ). 
 openStream 
 ()); 
  
 } 
  
 catch 
  
 ( 
 IOException 
  
 e 
 ) 
  
 { 
  
 throw 
  
 new 
  
 RuntimeException 
 ( 
 "Failed to retrieve image data from URL: " 
  
 + 
  
 url 
 , 
  
 e 
 ); 
  
 } 
  
 // Builds the image asset. 
  
 Asset 
  
 asset 
  
 = 
  
 Asset 
 . 
 newBuilder 
 () 
  
 . 
 setResourceName 
 ( 
 ResourceNames 
 . 
 asset 
 ( 
 customerId 
 , 
  
 temporaryId 
 -- 
 )) 
  
 // Provide a unique friendly name to identify your asset. 
  
 // When there is an existing image asset with the same content but a different name, 
  
 // the 
  
 // new name will be dropped silently. 
  
 . 
 setName 
 ( 
  
 String 
 . 
 format 
 ( 
  
 "%s#%s" 
 , 
  
 assetFieldType 
 , 
  
 CodeSampleHelper 
 . 
 getShortPrintableDateTime 
 ())) 
  
 . 
 setImageAsset 
 ( 
 ImageAsset 
 . 
 newBuilder 
 (). 
 setData 
 ( 
 ByteString 
 . 
 copyFrom 
 ( 
 imageData 
 ))) 
  
 . 
 build 
 (); 
  
 // Adds an operation to create the Asset. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetOperation 
 ( 
 AssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 asset 
 )) 
  
 . 
 build 
 ()); 
  
 // Builds the AssetGroupAsset. 
  
 AssetGroupAsset 
  
 assetGroupAsset 
  
 = 
  
 AssetGroupAsset 
 . 
 newBuilder 
 () 
  
 // References the Asset above by resource name. 
  
 . 
 setAsset 
 ( 
 asset 
 . 
 getResourceName 
 ()) 
  
 . 
 setAssetGroup 
 ( 
 assetGroupResourceName 
 ) 
  
 . 
 setFieldType 
 ( 
 assetFieldType 
 ) 
  
 . 
 build 
 (); 
  
 // Adds an operation to link the Asset to the AssetGroup. 
  
 mutateOperations 
 . 
 add 
 ( 
  
 MutateOperation 
 . 
 newBuilder 
 () 
  
 . 
 setAssetGroupAssetOperation 
 ( 
  
 AssetGroupAssetOperation 
 . 
 newBuilder 
 (). 
 setCreate 
 ( 
 assetGroupAsset 
 )) 
  
 . 
 build 
 ()); 
  
 } 
  
 } 
  
 return 
  
 mutateOperations 
 ; 
  
 } 
  
 /** 
 * 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. 
 * 
 * @param response the mutate Google Ads response. 
 */ 
  
 private 
  
 void 
  
 printResponseDetails 
 ( 
 MutateGoogleAdsResponse 
  
 response 
 ) 
  
 { 
  
 for 
  
 ( 
 MutateOperationResponse 
  
 operationResponse 
  
 : 
  
 response 
 . 
 getMutateOperationResponsesList 
 ()) 
  
 { 
  
 ResponseCase 
  
 responseCase 
  
 = 
  
 operationResponse 
 . 
 getResponseCase 
 (); 
  
 String 
  
 resourceName 
 ; 
  
 switch 
  
 ( 
 responseCase 
 ) 
  
 { 
  
 case 
  
 ASSET_RESULT 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 getAssetResult 
 (). 
 getResourceName 
 (); 
  
 break 
 ; 
  
 case 
  
 ASSET_GROUP_RESULT 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 getAssetGroupResult 
 (). 
 getResourceName 
 (); 
  
 break 
 ; 
  
 case 
  
 ASSET_GROUP_ASSET_RESULT 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 getAssetGroupAssetResult 
 (). 
 getResourceName 
 (); 
  
 break 
 ; 
  
 case 
  
 ASSET_SET_ASSET_RESULT 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 getAssetSetAssetResult 
 (). 
 getResourceName 
 (); 
  
 break 
 ; 
  
 case 
  
 CAMPAIGN_BUDGET_RESULT 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 getCampaignBudgetResult 
 (). 
 getResourceName 
 (); 
  
 break 
 ; 
  
 case 
  
 CAMPAIGN_RESULT 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 getCampaignResult 
 (). 
 getResourceName 
 (); 
  
 break 
 ; 
  
 default 
 : 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
 "Unexpected response case: " 
  
 + 
  
 responseCase 
 ); 
  
 } 
  
 System 
 . 
 out 
 . 
 printf 
 ( 
 "Created a(n) %s with resource name: '%s'%n" 
 , 
  
 responseCase 
 , 
  
 resourceName 
 ); 
  
 } 
  
 } 
 } 
  
  

C#

 // Copyright 2023 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.Gax.Util 
 ; 
 using 
  
 Google.Ads.GoogleAds.Config 
 ; 
 using 
  
 Google.Ads.GoogleAds.Lib 
 ; 
 using 
  
 Google.Ads.GoogleAds.V23.Common 
 ; 
 using 
  
 Google.Ads.GoogleAds.V23.Errors 
 ; 
 using 
  
 Google.Ads.GoogleAds.V23.Resources 
 ; 
 using 
  
 Google.Ads.GoogleAds.V23.Services 
 ; 
 using 
  
 Google.Protobuf 
 ; 
 using 
  
 System 
 ; 
 using 
  
 System.Collections.Generic 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 AdvertisingChannelTypeEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 AssetFieldTypeEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 AssetGroupStatusEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 AssetSetTypeEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 BudgetDeliveryMethodEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 CampaignStatusEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 EuPoliticalAdvertisingStatusEnum 
 . 
 Types 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 GoogleAds 
 . 
 V23 
 . 
 Enums 
 . 
 HotelAssetSuggestionStatusEnum 
 . 
 Types 
 ; 
 namespace 
  
 Google.Ads.GoogleAds.Examples.V23 
 { 
  
 /// <summary> 
  
 /// This example shows how to create a Performance Max for travel goals campaign. It also uses 
  
 /// TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case 
  
 /// there are not enough assets for the asset group (required by Performance Max), this example 
  
 /// will create more assets to fulfill the requirements. 
  
 /// 
  
 /// <p>For more information about Performance Max campaigns, see 
  
 /// https://developers.google.com/google-ads/api/docs/performance-max/overview.</p> 
  
 /// <p>Prerequisites:</p> 
  
 /// 
  
 /// <ul> 
  
 ///   <li>You must have at least one conversion action in the account. For more about conversion 
  
 ///       actions, see 
  
 ///       https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
  
 ///   </li> 
  
 /// </ul> 
  
 /// 
  
 /// <p>Notes:</p> 
  
 /// 
  
 /// <ul> 
  
 ///   <li>This example uses the default customer conversion goals. For an example of setting 
  
 ///       campaign-specific conversion goals, see AddPerformanceMaxRetailCampaign.cs.</li> 
  
 ///   <li>To learn how to create asset group signals, see AddPerformanceMaxCampaign.cs.</li> 
  
 /// </ul> 
  
 /// </summary> 
  
 public 
  
 class 
  
 AddPerformanceMaxForTravelGoalsCampaign 
  
 : 
  
 ExampleBase 
  
 { 
  
 /// <summary> 
  
 /// Command line options for running the 
  
 /// <see cref="AddPerformanceMaxForTravelGoalsCampaign"/> example. 
  
 /// </summary> 
  
 public 
  
 class 
  
 Options 
  
 : 
  
 OptionsBase 
  
 { 
  
 /// <summary> 
  
 /// The Google Ads customer ID. 
  
 /// </summary> 
  
 [Option("customerId", Required = true, HelpText = 
 "The Google Ads customer ID.")] 
  
 public 
  
 long 
  
 CustomerId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 /// <summary> 
  
 /// The place ID of a hotel property. A place ID uniquely identifies a place in the 
  
 /// Google Places database. See 
  
 /// https://developers.google.com/places/web-service-place-id to learn more. 
  
 /// </summary> 
  
 [Option("placeId", Required = true, HelpText = 
 "The place ID of a hotel property. A place ID uniquely identifies a place in the" + 
 "Google Places database. See " + 
 "https://developers.google.com/places/web-service-place-id to learn more.")] 
  
 public 
  
 string 
  
 PlaceId 
  
 { 
  
 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 
 ); 
  
 AddPerformanceMaxForTravelGoalsCampaign 
  
 codeExample 
  
 = 
  
 new 
  
 AddPerformanceMaxForTravelGoalsCampaign 
 (); 
  
 Console 
 . 
 WriteLine 
 ( 
 codeExample 
 . 
 Description 
 ); 
  
 codeExample 
 . 
 Run 
 ( 
 new 
  
 GoogleAdsClient 
 (), 
  
 options 
 . 
 CustomerId 
 , 
  
 options 
 . 
 PlaceId 
 ); 
  
 } 
  
 // Minimum requirements of assets required in a Performance Max asset group. 
  
 // See https://developers.google.com/google-ads/api/docs/performance-max/assets for details. 
  
 private 
  
 Dictionary<AssetFieldType 
 , 
  
 int 
>  
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
  
 = 
  
 new 
  
 Dictionary<AssetFieldType 
 , 
  
 int 
> () 
  
 { 
  
 { 
  
 AssetFieldType 
 . 
 Headline 
 , 
  
 3 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 LongHeadline 
 , 
  
 1 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 Description 
 , 
  
 2 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 BusinessName 
 , 
  
 1 
  
 }, 
  
 }; 
  
 private 
  
 Dictionary<AssetFieldType 
 , 
  
 int 
>  
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
  
 = 
  
 new 
  
 Dictionary<AssetFieldType 
 , 
  
 int 
> () 
  
 { 
  
 { 
  
 AssetFieldType 
 . 
 MarketingImage 
 , 
  
 1 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 SquareMarketingImage 
 , 
  
 1 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 Logo 
 , 
  
 1 
  
 }, 
  
 }; 
  
 // Texts and URLs used to create text and image assets when the TravelAssetSuggestionService 
  
 // doesn't return enough assets required for creating an asset group. 
  
 private 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
>  
 DEFAULT_TEXT_ASSETS_INFO 
  
 = 
  
 new 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
> () 
  
 { 
  
 { 
  
 AssetFieldType 
 . 
 Headline 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "Hotel" 
 , 
  
 "Travel Reviews" 
 , 
  
 "Book travel" 
  
 } 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 LongHeadline 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "Travel the World" 
  
 } 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 Description 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "Great deal for your beloved hotel" 
 , 
  
 "Best rate guaranteed" 
  
 } 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 BusinessName 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "Interplanetary cruises" 
  
 } 
  
 }, 
  
 }; 
  
 private 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
>  
 DEFAULT_IMAGE_ASSETS_INFO 
  
 = 
  
 new 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
> () 
  
 { 
  
 { 
  
 AssetFieldType 
 . 
 MarketingImage 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "https://gaagl.page.link/Eit5" 
  
 } 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 SquareMarketingImage 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "https://gaagl.page.link/bjYi" 
  
 } 
  
 }, 
  
 { 
  
 AssetFieldType 
 . 
 Logo 
 , 
  
 new 
  
 List<string> 
 () 
  
 { 
  
 "https://gaagl.page.link/bjYi" 
  
 } 
  
 }, 
  
 }; 
  
 // We specify temporary IDs that are specific to a single mutate request. Temporary IDs are always 
  
 // negative and unique within one mutate request. 
  
 // 
  
 // <p>See https://developers.google.com/google-ads/api/docs/mutating/best-practices for 
  
 // further details. 
  
 // 
  
 // <p>These temporary IDs are fixed because they are used in multiple places. 
  
 private 
  
 int 
  
 ASSET_TEMPORARY_ID 
  
 = 
  
 - 
 1 
 ; 
  
 private 
  
 int 
  
 BUDGET_TEMPORARY_ID 
  
 = 
  
 - 
 2 
 ; 
  
 private 
  
 int 
  
 CAMPAIGN_TEMPORARY_ID 
  
 = 
  
 - 
 3 
 ; 
  
 private 
  
 int 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 = 
  
 - 
 4 
 ; 
  
 // There are also entities that will be created in the same request but do not 
  
 // need to be fixed temporary IDs because they are referenced only once. 
  
 private 
  
 long 
  
 temporaryId 
  
 = 
  
 - 
 5 
 ; 
  
 /// <summary> 
  
 /// Returns a description about the code example. 
  
 /// </summary> 
  
 public 
  
 override 
  
 string 
  
 Description 
  
 = 
>  
 "This example shows how to create a Performance " 
  
 + 
  
 " Max for travel goals campaign. It also uses TravelAssetSuggestionService to fetch " 
  
 + 
  
 "suggested assets for creating an asset group. In case there are not enough assets " 
  
 + 
  
 "for the asset group (required by Performance Max), this example will create more " 
  
 + 
  
 "assets to fulfill the requirements.\n" 
  
 + 
  
 "For more information about Performance Max campaigns, see " 
  
 + 
  
 "https://developers.google.com/google-ads/api/docs/performance-max/overview.\n" 
  
 + 
  
 "Prerequisites:\n" 
  
 + 
  
 "You must have at least one conversion action in the account. For more about " 
  
 + 
  
 "conversion actions, see " 
  
 + 
  
 "https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions.\n" 
  
 + 
  
 "Notes:\n" 
  
 + 
  
 "- This example uses the default customer conversion goals. For an example of " 
  
 + 
  
 "setting campaign-specific conversion goals, see AddPerformanceMaxRetailCampaign.cs.\n" 
  
 + 
  
 "- To learn how to create asset group signals, see AddPerformanceMaxCampaign.cs." 
 ; 
  
 /// <summary> 
  
 /// Runs the code example. 
  
 /// </summary> 
  
 /// <param name="client">The Google Ads client.</param> 
  
 /// <param name="customerId">The Google Ads customer ID.</param> 
  
 /// <param name="placeId">The place ID of a hotel property.</param> 
  
 public 
  
 void 
  
 Run 
 ( 
 GoogleAdsClient 
  
 client 
 , 
  
 long 
  
 customerId 
 , 
  
 string 
  
 placeId 
 ) 
  
 { 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
  
 = 
  
 GetHotelAssetSuggestion 
 ( 
 client 
 , 
  
 customerId 
 , 
  
 placeId 
 ); 
  
 // Performance Max campaigns require that repeated assets such as headlines and 
  
 // descriptions be created before the campaign. For the list of required assets for a 
  
 // Performance Max campaign, see 
  
 // https://developers.google.com/google-ads/api/docs/performance-max/assets. 
  
 // This step is the same for any type of Performance Max campaign. 
  
 // Creates the headlines using the hotel asset suggestion. 
  
 List<string> 
  
 headlineAssetResourceNames 
  
 = 
  
 CreateMultipleTextAssets 
 ( 
  
 client 
 , 
  
 customerId 
 , 
  
 AssetFieldType 
 . 
 Headline 
 , 
  
 hotelAssetSuggestion 
 ); 
  
 // Creates the descriptions using the hotel asset suggestion. 
  
 List<string> 
  
 descriptionAssetResourceNames 
  
 = 
  
 CreateMultipleTextAssets 
 ( 
  
 client 
 , 
  
 customerId 
 , 
  
 AssetFieldType 
 . 
 Description 
 , 
  
 hotelAssetSuggestion 
 ); 
  
 // Creates a hotel property asset set, which will be used later to link with a newly 
  
 // created campaign. 
  
 string 
  
 hotelPropertyAssetSetResourceName 
  
 = 
  
 CreateHotelAssetSet 
 ( 
 client 
 , 
  
 customerId 
 ); 
  
 // Creates a hotel property asset and link it with the previously created hotel property 
  
 // asset set. This asset will also be linked to an asset group in the later steps. 
  
 // In the real-world scenario, you'd need to create many assets for all your hotel 
  
 // properties. We use one hotel property here for simplicity. 
  
 // Both asset and asset set need to be created before creating a campaign, so we cannot 
  
 // bundle them with other mutate operations below. 
  
 string 
  
 hotelPropertyAssetResourceName 
  
 = 
  
 CreateHotelAsset 
 ( 
  
 client 
 , 
  
 customerId 
 , 
  
 placeId 
 , 
  
 hotelPropertyAssetSetResourceName 
 ); 
  
 // It's important to create the entities below in this order because they depend on 
  
 // each other. 
  
 // The methods below create and return mutate operations that we later provide to the 
  
 // GoogleAdsService.Mutate method in order to create the entities in a single request. 
  
 // Since the entities for a Performance Max campaign are closely tied to one-another, 
  
 // it's considered a best practice to create them in a single Mutate request so they 
  
 // all complete successfully or fail entirely, leaving no orphaned entities. See: 
  
 // https://developers.google.com/google-ads/api/docs/mutating/overview. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 List<MutateOperation> 
 (); 
  
 mutateOperations 
 . 
 Add 
 ( 
 CreateCampaignBudgetOperation 
 ( 
 customerId 
 )); 
  
 mutateOperations 
 . 
 Add 
 ( 
 CreateCampaignOperation 
 ( 
 customerId 
 , 
  
 hotelPropertyAssetSetResourceName 
 )); 
  
 mutateOperations 
 . 
 AddRange 
 ( 
  
 CreateAssetGroupOperations 
 ( 
  
 customerId 
 , 
  
 hotelPropertyAssetResourceName 
 , 
  
 headlineAssetResourceNames 
 , 
  
 descriptionAssetResourceNames 
 , 
  
 hotelAssetSuggestion 
 , 
  
 client 
 . 
 Config 
  
 ) 
  
 ); 
  
 // Issues a mutate request to create everything and prints the results. 
  
 GoogleAdsServiceClient 
  
 googleAdsServiceClient 
  
 = 
  
 client 
 . 
 GetService 
 ( 
 Services 
 . 
 V23 
 . 
 GoogleAdsService 
 ); 
  
 MutateGoogleAdsResponse 
  
 response 
  
 = 
  
 googleAdsServiceClient 
 . 
 Mutate 
 ( 
 customerId 
 . 
 ToString 
 (), 
  
 mutateOperations 
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 "Created the following entities for a campaign budget, a campaign, " 
  
 + 
  
 "and an asset group for Performance Max for travel goals:" 
 ); 
  
 PrintResponseDetails 
 ( 
 response 
 ); 
  
 } 
  
 /// <summary> 
  
 /// Returns hotel asset suggestion obtained from TravelAssetsSuggestionService. 
  
 /// <param name="client">The Google Ads client.</param> 
  
 /// <param name="customerId">The Google Ads customer ID.</param> 
  
 /// <param name="placeId">The place ID of a hotel property.</param> 
  
 /// <returns>The hotel asset suggestion.</returns> 
  
 /// </summary> 
  
 private 
  
 HotelAssetSuggestion 
  
 GetHotelAssetSuggestion 
 ( 
 GoogleAdsClient 
  
 client 
 , 
  
 long 
  
 customerId 
 , 
  
 string 
  
 placeId 
 ) 
  
 { 
  
 // Get the TravelAssetSuggestionService client. 
  
 TravelAssetSuggestionServiceClient 
  
 travelAssetSuggestionService 
  
 = 
  
 client 
 . 
 GetService 
 ( 
 Services 
 . 
 V23 
 . 
 TravelAssetSuggestionService 
 ); 
  
 SuggestTravelAssetsRequest 
  
 request 
  
 = 
  
 new 
  
 SuggestTravelAssetsRequest 
  
 { 
  
 CustomerId 
  
 = 
  
 customerId 
 . 
 ToString 
 (), 
  
 LanguageOption 
  
 = 
  
 "en-US" 
 , 
  
 }; 
  
 request 
 . 
 PlaceIds 
 . 
 Add 
 ( 
 placeId 
 ); 
  
 SuggestTravelAssetsResponse 
  
 response 
  
 = 
  
 travelAssetSuggestionService 
 . 
 SuggestTravelAssets 
 ( 
  
 request 
  
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 $"Fetched a hotel asset suggestion for the place ID {placeId}" 
 ); 
  
 return 
  
 response 
 . 
 HotelAssetSuggestions 
 [ 
 0 
 ]; 
  
 } 
  
 ///<summary> 
  
 /// Creates multiple text assets and returns the list of resource names. The hotel asset 
  
 /// suggestion is used to create a text asset first. If the number of created text assets is 
  
 /// still fewer than the minimum required number of assets of the specified asset field 
  
 /// type, adds more text assets  to fulfill the requirement. 
  
 /// <param name="client">The Google Ads client.</param> 
  
 /// <param name="customerId">The Google Ads customer ID.</param> 
  
 /// <param name="assetFieldType">The asset field type that the text assets will be created 
  
 /// for.</param> 
  
 /// <param name="hotelAssetSuggestion">The hotel asset suggestion.</param> 
  
 /// <returns>The resource names of the created text assets.</returns> 
  
 /// </summary> 
  
 private 
  
 List<string> 
  
 CreateMultipleTextAssets 
 ( 
 GoogleAdsClient 
  
 client 
 , 
  
 long 
  
 customerId 
 , 
  
 AssetFieldType 
  
 assetFieldType 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 ) 
  
 { 
  
 // Uses the GoogleAdService to create multiple text assets in a single request. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 List<MutateOperation> 
 (); 
  
 // First, adds all the text assets of the specified asset field type. 
  
 // Filters to only the specified asset field type. 
  
 // Constructs a mutate operation to create the asset. 
  
 // Adds the operation to the list. 
  
 if 
  
 ( 
 hotelAssetSuggestion 
 . 
 Status 
  
 == 
  
 HotelAssetSuggestionStatus 
 . 
 Success 
 ) 
  
 { 
  
 foreach 
  
 ( 
 HotelTextAsset 
  
 asset 
  
 in 
  
 hotelAssetSuggestion 
 . 
 TextAssets 
 ) 
  
 { 
  
 if 
  
 ( 
 asset 
 . 
 AssetFieldType 
  
 == 
  
 assetFieldType 
 ) 
  
 { 
  
 MutateOperation 
  
 operation 
  
 = 
  
 new 
  
 MutateOperation 
  
 { 
  
 AssetOperation 
  
 = 
  
 new 
  
 AssetOperation 
  
 { 
  
 Create 
  
 = 
  
 new 
  
 Asset 
  
 { 
  
 TextAsset 
  
 = 
  
 new 
  
 TextAsset 
  
 { 
  
 Text 
  
 = 
  
 asset 
 . 
 Text 
  
 } 
  
 } 
  
 } 
  
 }; 
  
 mutateOperations 
 . 
 Add 
 ( 
 operation 
 ); 
  
 } 
  
 } 
  
 } 
  
 // If the added assets are still less than the minimum required assets for the asset 
  
 // field type, add more text assets using the default texts. 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 while 
  
 ( 
 mutateOperations 
 . 
 Count 
 < 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 [ 
 assetFieldType 
 ]) 
  
 { 
  
 string 
  
 text 
  
 = 
  
 DEFAULT_TEXT_ASSETS_INFO 
 [ 
 assetFieldType 
 ][ 
 i 
 ++ 
 ]; 
  
 MutateOperation 
  
 operation 
  
 = 
  
 new 
  
 MutateOperation 
  
 { 
  
 AssetOperation 
  
 = 
  
 new 
  
 AssetOperation 
  
 { 
  
 Create 
  
 = 
  
 new 
  
 Asset 
  
 { 
  
 TextAsset 
  
 = 
  
 new 
  
 TextAsset 
  
 { 
  
 Text 
  
 = 
  
 text 
  
 } 
  
 } 
  
 } 
  
 }; 
  
 mutateOperations 
 . 
 Add 
 ( 
 operation 
 ); 
  
 } 
  
 GoogleAdsServiceClient 
  
 googleAdsService 
  
 = 
  
 client 
 . 
 GetService 
 ( 
 Services 
 . 
 V23 
 . 
 GoogleAdsService 
 ); 
  
 MutateGoogleAdsResponse 
  
 response 
  
 = 
  
 googleAdsService 
 . 
 Mutate 
 ( 
 customerId 
 . 
 ToString 
 (), 
  
 mutateOperations 
 ); 
  
 List<string> 
  
 assetResourceNames 
  
 = 
  
 new 
  
 List<string> 
 (); 
  
 foreach 
  
 ( 
 MutateOperationResponse 
  
 operationResponse 
  
 in 
  
 response 
 . 
 MutateOperationResponses 
 ) 
  
 { 
  
 MutateAssetResult 
  
 assetResult 
  
 = 
  
 operationResponse 
 . 
 AssetResult 
 ; 
  
 assetResourceNames 
 . 
 Add 
 ( 
 assetResult 
 . 
 ResourceName 
 ); 
  
 } 
  
 Console 
 . 
 WriteLine 
 ( 
 $"The following assets were created for the asset field type " 
  
 + 
  
 $"{assetFieldType}" 
 ); 
  
 PrintResponseDetails 
 ( 
 response 
 ); 
  
 return 
  
 assetResourceNames 
 ; 
  
 } 
  
 /// <summary> 
  
 /// Creates a hotel property asset set. 
  
 /// <param name="client">The Google Ads client.</param> 
  
 /// <param name="customerId">The Google Ads customer ID.</param> 
  
 /// <returns> The created hotel property asset set resource name.</returns> 
  
 /// </summary> 
  
 private 
  
 string 
  
 CreateHotelAssetSet 
 ( 
 GoogleAdsClient 
  
 client 
 , 
  
 long 
  
 customerId 
 ) 
  
 { 
  
 AssetSetOperation 
  
 operation 
  
 = 
  
 new 
  
 AssetSetOperation 
 () 
  
 { 
  
 Create 
  
 = 
  
 new 
  
 AssetSet 
  
 { 
  
 Name 
  
 = 
  
 "My Hotel property asset set #" 
  
 + 
  
 ExampleUtilities 
 . 
 GetRandomString 
 (), 
  
 Type 
  
 = 
  
 AssetSetType 
 . 
 HotelProperty 
  
 } 
  
 }; 
  
 AssetSetServiceClient 
  
 assetSetService 
  
 = 
  
 client 
 . 
 GetService 
 ( 
 Services 
 . 
 V23 
 . 
 AssetSetService 
 ); 
  
 MutateAssetSetsResponse 
  
 response 
  
 = 
  
 assetSetService 
 . 
 MutateAssetSets 
 ( 
  
 customerId 
 . 
 ToString 
 (), 
  
 new 
  
 List<AssetSetOperation> 
  
 { 
  
 operation 
  
 } 
  
 ); 
  
 string 
  
 assetResourceName 
  
 = 
  
 response 
 . 
 Results 
 [ 
 0 
 ]. 
 ResourceName 
 ; 
  
 Console 
 . 
 WriteLine 
 ( 
 $"Created an asset set with resource name: {assetResourceName}" 
 ); 
  
 return 
  
 assetResourceName 
 ; 
  
 } 
  
 /// <summary> 
  
 /// Creates a hotel property asset using the specified place ID. The place ID must belong 
  
 /// to a hotel property. Then, links it to the specified asset set. 
  
 /// 
  
 /// <p>See https://developers.google.com/places/web-service/place-id to search for a hotel 
  
 /// place ID.</p> 
  
 /// <param name="client">The Google Ads API client.</param> 
  
 /// <param name="customerId">The client customer ID.</param> 
  
 /// <param name="placeId">The place ID for a hotel.</param> 
  
 /// <param name="hotelPropertyAssetSetResourceName">The hotel asset set resource 
  
 /// name.</param> 
  
 /// <returns>The created hotel property asset resource name.</returns> 
  
 /// </summary> 
  
 private 
  
 string 
  
 CreateHotelAsset 
 ( 
  
 GoogleAdsClient 
  
 client 
 , 
  
 long 
  
 customerId 
 , 
  
 string 
  
 placeId 
 , 
  
 string 
  
 hotelPropertyAssetSetResourceName 
 ) 
  
 { 
  
 // Uses the GoogleAdService to create an asset and asset set asset in a single request. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 List<MutateOperation> 
 (); 
  
 string 
  
 assetResourceName 
  
 = 
  
 ResourceNames 
 . 
 Asset 
 ( 
 customerId 
 , 
  
 ASSET_TEMPORARY_ID 
 ); 
  
 // Creates a mutate operation for a hotel property asset. 
  
 Asset 
  
 hotelPropertyAsset 
  
 = 
  
 new 
  
 Asset 
 () 
  
 { 
  
 ResourceName 
  
 = 
  
 assetResourceName 
 , 
  
 HotelPropertyAsset 
  
 = 
  
 new 
  
 HotelPropertyAsset 
  
 { 
  
 PlaceId 
  
 = 
  
 placeId 
  
 } 
  
 }; 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetOperation 
  
 = 
  
 new 
  
 AssetOperation 
  
 { 
  
 Create 
  
 = 
  
 hotelPropertyAsset 
  
 } 
  
 }); 
  
 // Creates a mutate operation for an asset set asset. 
  
 AssetSetAsset 
  
 assetSetAsset 
  
 = 
  
 new 
  
 AssetSetAsset 
  
 { 
  
 Asset 
  
 = 
  
 assetResourceName 
 , 
  
 AssetSet 
  
 = 
  
 hotelPropertyAssetSetResourceName 
  
 }; 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetSetAssetOperation 
  
 = 
  
 new 
  
 AssetSetAssetOperation 
  
 { 
  
 Create 
  
 = 
  
 assetSetAsset 
  
 } 
  
 }); 
  
 // Issues a mutate request to create all entities. 
  
 GoogleAdsServiceClient 
  
 googleAdsServiceClient 
  
 = 
  
 client 
 . 
 GetService 
 ( 
 Services 
 . 
 V23 
 . 
 GoogleAdsService 
 ); 
  
 MutateGoogleAdsResponse 
  
 response 
  
 = 
  
 googleAdsServiceClient 
 . 
 Mutate 
 ( 
 customerId 
 . 
 ToString 
 (), 
  
 mutateOperations 
 ); 
  
 Console 
 . 
 WriteLine 
 ( 
 "Created the following entities for the hotel asset:" 
 ); 
  
 PrintResponseDetails 
 ( 
 response 
 ); 
  
 return 
  
 response 
 . 
 MutateOperationResponses 
 [ 
 0 
 ]. 
 AssetResult 
 . 
 ResourceName 
 ; 
  
 } 
  
 /// <summary> 
  
 /// Creates a mutate operation that creates a new campaign budget. 
  
 /// <p>A temporary ID will be assigned to this campaign budget so that it can be referenced 
  
 /// by other objects being created in the same mutate request.</p> 
  
 /// <param name="customerId">The client customer ID.</param> 
  
 /// <returns>A mutate operation that creates a campaign budget.</returns> 
  
 /// </summary> 
  
 private 
  
 MutateOperation 
  
 CreateCampaignBudgetOperation 
 ( 
 long 
  
 customerId 
 ) 
  
 { 
  
 CampaignBudget 
  
 campaignBudget 
  
 = 
  
 new 
  
 CampaignBudget 
  
 { 
  
 Name 
  
 = 
  
 "Performance Max for travel goals campaign budget #" 
  
 + 
  
 ExampleUtilities 
 . 
 GetRandomString 
 (), 
  
 // The budget period already defaults to DAILY. 
  
 AmountMicros 
  
 = 
  
 500000 
 , 
  
 DeliveryMethod 
  
 = 
  
 BudgetDeliveryMethod 
 . 
 Standard 
 , 
  
 // A Performance Max campaign cannot use a shared campaign budget. 
  
 ExplicitlyShared 
  
 = 
  
 false 
 , 
  
 // Sets a temporary ID in the budget's resource name, so it can be referenced 
  
 // by the campaign in later steps. 
  
 ResourceName 
  
 = 
  
 ResourceNames 
 . 
 CampaignBudget 
 ( 
 customerId 
 , 
  
 BUDGET_TEMPORARY_ID 
 ) 
  
 }; 
  
 return 
  
 new 
  
 MutateOperation 
  
 { 
  
 CampaignBudgetOperation 
  
 = 
  
 new 
  
 CampaignBudgetOperation 
  
 { 
  
 Create 
  
 = 
  
 campaignBudget 
  
 } 
  
 }; 
  
 } 
  
 /// <summary> 
  
 /// Creates a mutate operation that creates a new Performance Max campaign. Links the 
  
 /// specified hotel property asset set to this campaign. 
  
 /// <p>A temporary ID will be assigned to this campaign so that it can be referenced by 
  
 /// other objects being created in the same mutate request.</p> 
  
 /// <param name="customerId">The client customer ID.</param> 
  
 /// <param name="hotelPropertyAssetSetResourceName"> The resource name of the hotel property 
  
 /// asset set.</param> 
  
 /// <returns>A mutate operation that creates a campaign.</returns> 
  
 /// </summary> 
  
 private 
  
 MutateOperation 
  
 CreateCampaignOperation 
 ( 
 long 
  
 customerId 
 , 
  
 string 
  
 hotelPropertyAssetSetResourceName 
 ) 
  
 { 
  
 Campaign 
  
 performanceMaxCampaign 
  
 = 
  
 new 
  
 Campaign 
  
 { 
  
 Name 
  
 = 
  
 "Performance Max for travel goals campaign #" 
  
 + 
  
 ExampleUtilities 
 . 
 GetRandomString 
 (), 
  
 // Sets the campaign status as PAUSED. The campaign is the only entity in 
  
 // the mutate request that should have its status set. 
  
 Status 
  
 = 
  
 CampaignStatus 
 . 
 Paused 
 , 
  
 // All Performance Max campaigns have an advertising_channel_type of 
  
 // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. 
  
 AdvertisingChannelType 
  
 = 
  
 AdvertisingChannelType 
 . 
 PerformanceMax 
 , 
  
 // To create a Performance Max for travel goals campaign, you need to set 
  
 // `hotel_property_asset_set`. 
  
 HotelPropertyAssetSet 
  
 = 
  
 hotelPropertyAssetSetResourceName 
 , 
  
 // Bidding strategy must be set directly on the campaign. 
  
 // Setting a portfolio bidding strategy by resource name is not supported. 
  
 // Max Conversion and Maximize Conversion Value are the only strategies 
  
 // supported for Performance Max campaigns. 
  
 // An optional ROAS (Return on Advertising Spend) can be set for 
  
 // maximize_conversion_value. The ROAS value must be specified as a ratio in 
  
 // the API. It is calculated by dividing "total value" by "total spend". 
  
 // For more information on Maximize Conversion Value, see the support 
  
 // article: http://support.google.com/google-ads/answer/7684216. 
  
 // A targetRoas of 3.5 corresponds to a 350% return on ad spend. 
  
 MaximizeConversionValue 
  
 = 
  
 new 
  
 MaximizeConversionValue 
  
 { 
  
 TargetRoas 
  
 = 
  
 3.5 
  
 }, 
  
 // Assigns the resource name with a temporary ID. 
  
 ResourceName 
  
 = 
  
 ResourceNames 
 . 
 Campaign 
 ( 
 customerId 
 , 
  
 CAMPAIGN_TEMPORARY_ID 
 ), 
  
 // Sets the budget using the given budget resource name. 
  
 CampaignBudget 
  
 = 
  
 ResourceNames 
 . 
 CampaignBudget 
 ( 
 customerId 
 , 
  
 BUDGET_TEMPORARY_ID 
 ), 
  
 // Declare whether or not this campaign contains political ads targeting the EU. 
  
 ContainsEuPoliticalAdvertising 
  
 = 
  
 EuPoliticalAdvertisingStatus 
 . 
 DoesNotContainEuPoliticalAdvertising 
 , 
  
 }; 
  
 return 
  
 new 
  
 MutateOperation 
  
 { 
  
 CampaignOperation 
  
 = 
  
 new 
  
 CampaignOperation 
  
 { 
  
 Create 
  
 = 
  
 performanceMaxCampaign 
  
 } 
  
 }; 
  
 } 
  
 /// <summary> 
  
 /// Creates a mutate operation that creates a new asset group. 
  
 /// <param name="customerId">The client customer ID.</param> 
  
 /// <param name="hotelPropertyAssetResourceName"> The resource name of the hotel property 
  
 /// asset.</param> 
  
 /// <param name="headlineAssetResourceNames">The resource names for headline 
  
 /// assets.</param> 
  
 /// <param name="descriptionAssetResourceNames">The resource names for description 
  
 /// assets.</param> 
  
 /// <param name="hotelAssetSuggestion">The hotel asset suggestion.</param> 
  
 /// <param name="config">The Google Ads configuration.</param> 
  
 /// <returns>A mutate operation that creates an asset group.</returns> 
  
 /// </summary> 
  
 private 
  
 List<MutateOperation> 
  
 CreateAssetGroupOperations 
 ( 
  
 long 
  
 customerId 
 , 
  
 string 
  
 hotelPropertyAssetResourceName 
 , 
  
 List<string> 
  
 headlineAssetResourceNames 
 , 
  
 List<string> 
  
 descriptionAssetResourceNames 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 , 
  
 GoogleAdsConfig 
  
 config 
  
 ) 
  
 { 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 List<MutateOperation> 
 (); 
  
 // Creates a new mutate operation that creates an asset group using suggested 
  
 // information when available. 
  
 string 
  
 assetGroupName 
 ; 
  
 List<string> 
  
 assetGroupFinalUrls 
  
 = 
  
 new 
  
 List<string> 
 (); 
  
 if 
  
 ( 
 hotelAssetSuggestion 
 . 
 Status 
  
 == 
  
 HotelAssetSuggestionStatus 
 . 
 Success 
 ) 
  
 { 
  
 assetGroupName 
  
 = 
  
 hotelAssetSuggestion 
 . 
 HotelName 
 ; 
  
 assetGroupFinalUrls 
 . 
 Add 
 ( 
 hotelAssetSuggestion 
 . 
 FinalUrl 
 ); 
  
 } 
  
 else 
  
 { 
  
 assetGroupName 
  
 = 
  
 "Performance Max for travel goals asset group #" 
  
 + 
  
 ExampleUtilities 
 . 
 GetRandomString 
 (); 
  
 assetGroupFinalUrls 
 . 
 Add 
 ( 
 "https://www.example.com" 
 ); 
  
 } 
  
 string 
  
 assetGroupResourceName 
  
 = 
  
 ResourceNames 
 . 
 AssetGroup 
 ( 
 customerId 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ); 
  
 AssetGroup 
  
 assetGroup 
  
 = 
  
 new 
  
 AssetGroup 
  
 { 
  
 ResourceName 
  
 = 
  
 assetGroupResourceName 
 , 
  
 Name 
  
 = 
  
 assetGroupName 
 , 
  
 Campaign 
  
 = 
  
 ResourceNames 
 . 
 Campaign 
 ( 
 customerId 
 , 
  
 CAMPAIGN_TEMPORARY_ID 
 ), 
  
 Status 
  
 = 
  
 AssetGroupStatus 
 . 
 Paused 
  
 }; 
  
 assetGroup 
 . 
 FinalUrls 
 . 
 AddRange 
 ( 
 assetGroupFinalUrls 
 ); 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetGroupOperation 
  
 = 
  
 new 
  
 AssetGroupOperation 
  
 { 
  
 Create 
  
 = 
  
 assetGroup 
  
 } 
  
 }); 
  
 // An asset group is linked to an asset by creating a new asset group asset 
  
 // and providing: 
  
 // -  the resource name of the asset group 
  
 // -  the resource name of the asset 
  
 // -  the field_type of the asset in this asset group 
  
 // 
  
 // To learn more about asset groups, see 
  
 // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. 
  
 // Headline and description assets were created at the first step of this example. 
  
 // So, we just need to link them with the created asset group. 
  
 List<AssetGroupAsset> 
  
 assetGroupAssets 
  
 = 
  
 new 
  
 List<AssetGroupAsset> 
 (); 
  
 foreach 
  
 ( 
 string 
  
 headlineAssetResourceName 
  
 in 
  
 headlineAssetResourceNames 
 ) 
  
 { 
  
 assetGroupAssets 
 . 
 Add 
 ( 
 new 
  
 AssetGroupAsset 
  
 { 
  
 Asset 
  
 = 
  
 headlineAssetResourceName 
 , 
  
 AssetGroup 
  
 = 
  
 assetGroupResourceName 
 , 
  
 FieldType 
  
 = 
  
 AssetFieldType 
 . 
 Headline 
  
 }); 
  
 } 
  
 foreach 
  
 ( 
 string 
  
 descriptionAssetResourceName 
  
 in 
  
 descriptionAssetResourceNames 
 ) 
  
 { 
  
 assetGroupAssets 
 . 
 Add 
 ( 
 new 
  
 AssetGroupAsset 
  
 { 
  
 Asset 
  
 = 
  
 descriptionAssetResourceName 
 , 
  
 AssetGroup 
  
 = 
  
 assetGroupResourceName 
 , 
  
 FieldType 
  
 = 
  
 AssetFieldType 
 . 
 Description 
  
 }); 
  
 } 
  
 foreach 
  
 ( 
 AssetGroupAsset 
  
 assetGroupAsset 
  
 in 
  
 assetGroupAssets 
 ) 
  
 { 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetGroupAssetOperation 
  
 = 
  
 new 
  
 AssetGroupAssetOperation 
  
 { 
  
 Create 
  
 = 
  
 assetGroupAsset 
  
 } 
  
 }); 
  
 } 
  
 // Link the previously created hotel property asset to the asset group. In the 
  
 // real-world scenario, you'd need to do this step several times for each hotel property 
  
 // asset. 
  
 AssetGroupAsset 
  
 hotelPropertyAssetGroupAsset 
  
 = 
  
 new 
  
 AssetGroupAsset 
  
 { 
  
 Asset 
  
 = 
  
 hotelPropertyAssetResourceName 
 , 
  
 AssetGroup 
  
 = 
  
 assetGroupResourceName 
 , 
  
 FieldType 
  
 = 
  
 AssetFieldType 
 . 
 HotelProperty 
  
 }; 
  
 // Adds an operation to link the hotel property asset to the asset group. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetGroupAssetOperation 
  
 = 
  
 new 
  
 AssetGroupAssetOperation 
  
 { 
  
 Create 
  
 = 
  
 hotelPropertyAssetGroupAsset 
  
 } 
  
 }); 
  
 // Creates the rest of the required text assets and links them to the asset group. 
  
 mutateOperations 
 . 
 AddRange 
 ( 
  
 CreateOperationsForTextAssetsAndAssetGroupAssets 
 ( 
  
 customerId 
 , 
  
 hotelAssetSuggestion 
 , 
  
 assetGroupResourceName 
  
 ) 
  
 ); 
  
 // Creates the image assets and links them to the asset group. Some optional image 
  
 // assets suggested by the TravelAssetSuggestionService might be created too. 
  
 mutateOperations 
 . 
 AddRange 
 ( 
  
 CreateOperationsForImageAssetsAndAssetGroupAssets 
 ( 
  
 customerId 
 , 
  
 hotelAssetSuggestion 
 , 
  
 assetGroupResourceName 
 , 
  
 config 
  
 ) 
  
 ); 
  
 if 
  
 ( 
 hotelAssetSuggestion 
 . 
 Status 
  
 == 
  
 HotelAssetSuggestionStatus 
 . 
 Success 
 ) 
  
 { 
  
 // Creates a new mutate operation for a suggested call-to-action asset and links it 
  
 // to the asset group. 
  
 Asset 
  
 callToActionAsset 
  
 = 
  
 new 
  
 Asset 
  
 { 
  
 ResourceName 
  
 = 
  
 ResourceNames 
 . 
 Asset 
 ( 
 customerId 
 , 
  
 temporaryId 
 ), 
  
 Name 
  
 = 
  
 "Suggested call-to-action asset #" 
  
 + 
  
 ExampleUtilities 
 . 
 GetRandomString 
 (), 
  
 CallToActionAsset 
  
 = 
  
 new 
  
 CallToActionAsset 
  
 { 
  
 CallToAction 
  
 = 
  
 hotelAssetSuggestion 
 . 
 CallToAction 
  
 } 
  
 }; 
  
 // Adds an operation to create the call-to-action asset. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetOperation 
  
 = 
  
 new 
  
 AssetOperation 
  
 { 
  
 Create 
  
 = 
  
 callToActionAsset 
  
 } 
  
 }); 
  
 AssetGroupAsset 
  
 callToActionAssetGroupAsset 
  
 = 
  
 new 
  
 AssetGroupAsset 
  
 { 
  
 Asset 
  
 = 
  
 callToActionAsset 
 . 
 ResourceName 
 , 
  
 AssetGroup 
  
 = 
  
 assetGroupResourceName 
 , 
  
 FieldType 
  
 = 
  
 AssetFieldType 
 . 
 CallToActionSelection 
  
 }; 
  
 // Adds an operation to link the call-to-action asset to the asset group. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetGroupAssetOperation 
  
 = 
  
 new 
  
 AssetGroupAssetOperation 
  
 { 
  
 Create 
  
 = 
  
 callToActionAssetGroupAsset 
  
 } 
  
 }); 
  
 temporaryId 
 -- 
 ; 
  
 } 
  
 return 
  
 mutateOperations 
 ; 
  
 } 
  
 /// <summary> 
  
 /// Creates text assets required for an asset group using the suggested hotel text assets. 
  
 /// It adds  more text assets to fulfill the requirements if the suggested hotel text assets 
  
 /// are not enough. 
  
 /// <param name="customerId">The client customer ID.</param> 
  
 /// <param name="hotelAssetSuggestion">The hotel asset suggestion.</param> 
  
 /// <param name="assetGroupResourceName">The resource name of the asset group.</param> 
  
 /// <returns>A list of mutate operations that create text assets and asset group 
  
 /// assets.</returns> 
  
 /// </summary> 
  
 private 
  
 List<MutateOperation> 
  
 CreateOperationsForTextAssetsAndAssetGroupAssets 
 ( 
  
 long 
  
 customerId 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 , 
  
 string 
  
 assetGroupResourceName 
  
 ) 
  
 { 
  
 // Creates mutate operations for the suggested text assets except for headlines and 
  
 // descriptions, which were created previously. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 List<MutateOperation> 
 (); 
  
 // Creates a map of asset field type to list of text values to create. 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
>  
 textByFieldType 
  
 = 
  
 new 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
> (); 
  
 if 
  
 ( 
 hotelAssetSuggestion 
 . 
 Status 
  
 == 
  
 HotelAssetSuggestionStatus 
 . 
 Success 
 ) 
  
 { 
  
 // Adds text values of suggested text assets. 
  
 foreach 
  
 ( 
 HotelTextAsset 
  
 hotelTextAsset 
  
 in 
  
 hotelAssetSuggestion 
 . 
 TextAssets 
 ) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 hotelTextAsset 
 . 
 AssetFieldType 
 ; 
  
 if 
  
 ( 
 assetFieldType 
  
 == 
  
 AssetFieldType 
 . 
 Headline 
  
 || 
  
 assetFieldType 
  
 == 
  
 AssetFieldType 
 . 
 Description 
 ) 
  
 { 
  
 // Headlines and descriptions were already created at the first step of this 
  
 // code example. 
  
 continue 
 ; 
  
 } 
  
 Console 
 . 
 WriteLine 
 ( 
 $"A text asset with text {hotelTextAsset.Text} is " 
  
 + 
  
 $"suggested for the asset field type {assetFieldType}" 
 ); 
  
 List<string> 
  
 existingTexts 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 ! 
 textByFieldType 
 . 
 TryGetValue 
 ( 
 assetFieldType 
 , 
  
 out 
  
 existingTexts 
 )) 
  
 { 
  
 existingTexts 
  
 = 
  
 textByFieldType 
 [ 
 assetFieldType 
 ] 
  
 = 
  
 new 
  
 List<string> 
 (); 
  
 } 
  
 existingTexts 
 . 
 Add 
 ( 
 hotelTextAsset 
 . 
 Text 
 ); 
  
 } 
  
 } 
  
 // Collects more text values by field type to fulfill the requirements. 
  
 foreach 
  
 ( 
 AssetFieldType 
  
 assetFieldType 
  
 in 
  
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 . 
 Keys 
 ) 
  
 { 
  
 if 
  
 ( 
 assetFieldType 
  
 == 
  
 AssetFieldType 
 . 
 Headline 
  
 || 
  
 assetFieldType 
  
 == 
  
 AssetFieldType 
 . 
 Description 
 ) 
  
 { 
  
 // Headlines and descriptions were already created at the first step of 
  
 // this code example. 
  
 continue 
 ; 
  
 } 
  
 List<string> 
  
 existingTexts 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 ! 
 textByFieldType 
 . 
 TryGetValue 
 ( 
 assetFieldType 
 , 
  
 out 
  
 existingTexts 
 )) 
  
 { 
  
 existingTexts 
  
 = 
  
 textByFieldType 
 [ 
 assetFieldType 
 ] 
  
 = 
  
 new 
  
 List<string> 
 (); 
  
 } 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 while 
  
 ( 
 textByFieldType 
 [ 
 assetFieldType 
 ]. 
 Count 
  
<  
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 [ 
 assetFieldType 
 ]) 
  
 { 
  
 string 
  
 textFromDefaults 
  
 = 
  
 DEFAULT_TEXT_ASSETS_INFO 
 [ 
 assetFieldType 
 ][ 
 i 
 ++ 
 ]; 
  
 Console 
 . 
 WriteLine 
 ( 
 $"A default text '{textFromDefaults}' is used to create a " 
  
 + 
  
 $"text asset for the asset field type '{assetFieldType}'" 
 ); 
  
 existingTexts 
 . 
 Add 
 ( 
 textFromDefaults 
 ); 
  
 } 
  
 } 
  
 // Converts the list of text values by field type into AssetOperations and 
  
 // AssetGroupAssetOperations. 
  
 foreach 
  
 ( 
 AssetFieldType 
  
 assetFieldType 
  
 in 
  
 textByFieldType 
 . 
 Keys 
 ) 
  
 { 
  
 foreach 
  
 ( 
 string 
  
 text 
  
 in 
  
 textByFieldType 
 [ 
 assetFieldType 
 ]) 
  
 { 
  
 // Builds the asset. 
  
 Asset 
  
 asset 
  
 = 
  
 new 
  
 Asset 
  
 { 
  
 ResourceName 
  
 = 
  
 ResourceNames 
 . 
 Asset 
 ( 
 customerId 
 , 
  
 temporaryId 
 -- 
 ), 
  
 TextAsset 
  
 = 
  
 new 
  
 TextAsset 
  
 { 
  
 Text 
  
 = 
  
 text 
  
 } 
  
 }; 
  
 // Adds an operation to create the Asset. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetOperation 
  
 = 
  
 new 
  
 AssetOperation 
  
 { 
  
 Create 
  
 = 
  
 asset 
  
 } 
  
 }); 
  
 //Builds the AssetGroupAsset. 
  
 AssetGroupAsset 
  
 assetGroupAsset 
  
 = 
  
 new 
  
 AssetGroupAsset 
  
 { 
  
 Asset 
  
 = 
  
 asset 
 . 
 ResourceName 
 , 
  
 AssetGroup 
  
 = 
  
 assetGroupResourceName 
 , 
  
 FieldType 
  
 = 
  
 assetFieldType 
  
 }; 
  
 // Adds an operation to link the Asset to the AssetGroup. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetGroupAssetOperation 
  
 = 
  
 new 
  
 AssetGroupAssetOperation 
  
 { 
  
 Create 
  
 = 
  
 assetGroupAsset 
  
 } 
  
 }); 
  
 } 
  
 } 
  
 return 
  
 mutateOperations 
 ; 
  
 } 
  
 /// <summary> 
  
 /// Creates image assets required for an asset group using the suggested hotel image assets. 
  
 /// It adds more image assets to fulfill the requirements if the suggested hotel image 
  
 /// assets are not enough. 
  
 /// <param name="customerId">The client customer ID.</param> 
  
 /// <param name="hotelAssetSuggestion">The hotel asset suggestion.</param> 
  
 /// <param name="assetGroupResourceName">The resource name of the asset group.</param> 
  
 /// <param name="config">The Google Ads config.</param> 
  
 /// <returns>A list of mutate operations that create image assets and asset group 
  
 /// assets.</returns> 
  
 /// </summary> 
  
 private 
  
 List<MutateOperation> 
  
 CreateOperationsForImageAssetsAndAssetGroupAssets 
 ( 
  
 long 
  
 customerId 
 , 
  
 HotelAssetSuggestion 
  
 hotelAssetSuggestion 
 , 
  
 string 
  
 assetGroupResourceName 
 , 
  
 GoogleAdsConfig 
  
 config 
  
 ) 
  
 { 
  
 // Creates mutate operations for the suggested image assets. 
  
 List<MutateOperation> 
  
 mutateOperations 
  
 = 
  
 new 
  
 List<MutateOperation> 
 (); 
  
 // Creates a map of asset field type to list of image URLs for which this method will 
  
 // create assets and asset group assets. 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
>  
 imageUrlsByFieldType 
  
 = 
  
 new 
  
 Dictionary<AssetFieldType 
 , 
  
 List<string> 
> (); 
  
 if 
  
 ( 
 hotelAssetSuggestion 
 . 
 Status 
  
 == 
  
 HotelAssetSuggestionStatus 
 . 
 Success 
 ) 
  
 { 
  
 // Adds URLs of suggested image assets. 
  
 foreach 
  
 ( 
 HotelImageAsset 
  
 hotelImageAsset 
  
 in 
  
 hotelAssetSuggestion 
 . 
 ImageAssets 
 ) 
  
 { 
  
 AssetFieldType 
  
 assetFieldType 
  
 = 
  
 hotelImageAsset 
 . 
 AssetFieldType 
 ; 
  
 Console 
 . 
 WriteLine 
 ( 
 $"An image asset with URL '{hotelImageAsset.Uri} is " 
  
 + 
  
 $"suggested for the asset field type {assetFieldType}" 
 ); 
  
 List<string> 
  
 existingImageUrls 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 ! 
 imageUrlsByFieldType 
 . 
 TryGetValue 
 ( 
 assetFieldType 
 , 
  
 out 
  
 existingImageUrls 
 )) 
  
 { 
  
 existingImageUrls 
  
 = 
  
 imageUrlsByFieldType 
 [ 
 assetFieldType 
 ] 
  
 = 
  
 new 
  
 List<string> 
 (); 
  
 } 
  
 existingImageUrls 
 . 
 Add 
 ( 
 hotelImageAsset 
 . 
 Uri 
 ); 
  
 } 
  
 } 
  
 // Collects more image URLs by field type to fulfill the requirements. 
  
 foreach 
  
 ( 
 AssetFieldType 
  
 assetFieldType 
  
 in 
  
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 . 
 Keys 
 ) 
  
 { 
  
 List<string> 
  
 existingImageUrls 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 ! 
 imageUrlsByFieldType 
 . 
 TryGetValue 
 ( 
 assetFieldType 
 , 
  
 out 
  
 existingImageUrls 
 )) 
  
 { 
  
 existingImageUrls 
  
 = 
  
 imageUrlsByFieldType 
 [ 
 assetFieldType 
 ] 
  
 = 
  
 new 
  
 List<string> 
 (); 
  
 } 
  
 int 
  
 i 
  
 = 
  
 0 
 ; 
  
 while 
  
 ( 
 imageUrlsByFieldType 
 [ 
 assetFieldType 
 ]. 
 Count 
  
<  
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 [ 
 assetFieldType 
 ]) 
  
 { 
  
 string 
  
 imageUrlFromDefaults 
  
 = 
  
 DEFAULT_IMAGE_ASSETS_INFO 
 [ 
 assetFieldType 
 ][ 
 i 
 ++ 
 ]; 
  
 Console 
 . 
 WriteLine 
 ( 
 $"A default image URL '{imageUrlFromDefaults} is used to " 
  
 + 
  
 $"create an image asset for the asset field type {assetFieldType}" 
 ); 
  
 existingImageUrls 
 . 
 Add 
 ( 
 imageUrlFromDefaults 
 ); 
  
 } 
  
 } 
  
 // Converts the list of URLs by field type into AssetOperations and 
  
 // AssetGroupAssetOperations. 
  
 foreach 
  
 ( 
 AssetFieldType 
  
 assetFieldType 
  
 in 
  
 imageUrlsByFieldType 
 . 
 Keys 
 ) 
  
 { 
  
 foreach 
  
 ( 
 string 
  
 imageUrl 
  
 in 
  
 imageUrlsByFieldType 
 [ 
 assetFieldType 
 ]) 
  
 { 
  
 // Builds the image asset. 
  
 Asset 
  
 asset 
  
 = 
  
 new 
  
 Asset 
  
 { 
  
 ResourceName 
  
 = 
  
 ResourceNames 
 . 
 Asset 
 ( 
 customerId 
 , 
  
 temporaryId 
 -- 
 ), 
  
 // Provide a unique friendly name to identify your asset. 
  
 // When there is an existing image asset with the same content but a 
  
 // different name, the new name will be dropped silently. 
  
 Name 
  
 = 
  
 $"{assetFieldType}{ExampleUtilities.GetRandomString()}" 
 , 
  
 ImageAsset 
  
 = 
  
 new 
  
 ImageAsset 
  
 { 
  
 Data 
  
 = 
  
 ByteString 
 . 
 CopyFrom 
 ( 
  
 MediaUtilities 
 . 
 GetAssetDataFromUrl 
 ( 
 imageUrl 
 , 
  
 config 
 ) 
  
 ) 
  
 } 
  
 }; 
  
 // Adds an operation to create the asset. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetOperation 
  
 = 
  
 new 
  
 AssetOperation 
  
 { 
  
 Create 
  
 = 
  
 asset 
  
 } 
  
 }); 
  
 // Builds the AssetGroupAsset 
  
 AssetGroupAsset 
  
 assetGroupAsset 
  
 = 
  
 new 
  
 AssetGroupAsset 
  
 { 
  
 Asset 
  
 = 
  
 asset 
 . 
 ResourceName 
 , 
  
 AssetGroup 
  
 = 
  
 assetGroupResourceName 
 , 
  
 FieldType 
  
 = 
  
 assetFieldType 
  
 }; 
  
 // Adds an operation to link the Asset to the AssetGroup. 
  
 mutateOperations 
 . 
 Add 
 ( 
 new 
  
 MutateOperation 
  
 { 
  
 AssetGroupAssetOperation 
  
 = 
  
 new 
  
 AssetGroupAssetOperation 
  
 { 
  
 Create 
  
 = 
  
 assetGroupAsset 
  
 } 
  
 }); 
  
 } 
  
 } 
  
 return 
  
 mutateOperations 
 ; 
  
 } 
  
 /// <summary> 
  
 /// Prints the details of a MutateGoogleAdsResponse. Parses the "response" field name and 
  
 /// uses it to extract the new entity's name and resource name. 
  
 /// <param name="response">The mutate Google Ads response.</param> 
  
 /// </summary> 
  
 private 
  
 void 
  
 PrintResponseDetails 
 ( 
 MutateGoogleAdsResponse 
  
 response 
 ) 
  
 { 
  
 foreach 
  
 ( 
 MutateOperationResponse 
  
 operationResponse 
  
 in 
  
 response 
 . 
 MutateOperationResponses 
 ) 
  
 { 
  
 string 
  
 resourceName 
 ; 
  
 string 
  
 entityName 
  
 = 
  
 operationResponse 
 . 
 ResponseCase 
 . 
 ToString 
 (); 
  
 // Trim the substring "Result" from the end of the entity name. 
  
 entityName 
  
 = 
  
 entityName 
 . 
 Remove 
 ( 
 entityName 
 . 
 Length 
  
 - 
  
 6 
 ); 
  
 switch 
  
 ( 
 operationResponse 
 . 
 ResponseCase 
 ) 
  
 { 
  
 case 
  
 MutateOperationResponse 
 . 
 ResponseOneofCase 
 . 
 AssetResult 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 AssetResult 
 . 
 ResourceName 
 ; 
  
 break 
 ; 
  
 case 
  
 MutateOperationResponse 
 . 
 ResponseOneofCase 
 . 
 AssetGroupResult 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 AssetGroupResult 
 . 
 ResourceName 
 ; 
  
 break 
 ; 
  
 case 
  
 MutateOperationResponse 
 . 
 ResponseOneofCase 
 . 
 AssetGroupAssetResult 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 AssetGroupAssetResult 
 . 
 ResourceName 
 ; 
  
 break 
 ; 
  
 case 
  
 MutateOperationResponse 
 . 
 ResponseOneofCase 
 . 
 AssetSetAssetResult 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 AssetSetAssetResult 
 . 
 ResourceName 
 ; 
  
 break 
 ; 
  
 case 
  
 MutateOperationResponse 
 . 
 ResponseOneofCase 
 . 
 CampaignBudgetResult 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 CampaignBudgetResult 
 . 
 ResourceName 
 ; 
  
 break 
 ; 
  
 case 
  
 MutateOperationResponse 
 . 
 ResponseOneofCase 
 . 
 CampaignResult 
 : 
  
 resourceName 
  
 = 
  
 operationResponse 
 . 
 CampaignResult 
 . 
 ResourceName 
 ; 
  
 break 
 ; 
  
 default 
 : 
  
 resourceName 
  
 = 
  
 "<not found>" 
 ; 
  
 break 
 ; 
  
 } 
  
 Console 
 . 
 WriteLine 
 ( 
  
 $"Created a(n) {entityName} with resource name: '{resourceName}'." 
 ); 
  
 } 
  
 } 
  
 } 
 } 
  
  

PHP

< ?php 
 /** 
 * Copyright 2023 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\Travel; 
 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\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\Common\CallToActionAsset; 
 use Google\Ads\GoogleAds\V23\Common\HotelPropertyAsset; 
 use Google\Ads\GoogleAds\V23\Common\ImageAsset; 
 use Google\Ads\GoogleAds\V23\Common\MaximizeConversionValue; 
 use Google\Ads\GoogleAds\V23\Common\TextAsset; 
 use Google\Ads\GoogleAds\V23\Enums\AdvertisingChannelTypeEnum\AdvertisingChannelType; 
 use Google\Ads\GoogleAds\V23\Enums\AssetFieldTypeEnum\AssetFieldType; 
 use Google\Ads\GoogleAds\V23\Enums\AssetGroupStatusEnum\AssetGroupStatus; 
 use Google\Ads\GoogleAds\V23\Enums\AssetSetTypeEnum\AssetSetType; 
 use Google\Ads\GoogleAds\V23\Enums\BudgetDeliveryMethodEnum\BudgetDeliveryMethod; 
 use Google\Ads\GoogleAds\V23\Enums\CampaignStatusEnum\CampaignStatus; 
 use Google\Ads\GoogleAds\V23\Enums\EuPoliticalAdvertisingStatusEnum\EuPoliticalAdvertisingStatus; 
 use Google\Ads\GoogleAds\V23\Enums\HotelAssetSuggestionStatusEnum\HotelAssetSuggestionStatus; 
 use Google\Ads\GoogleAds\V23\Errors\GoogleAdsError; 
 use Google\Ads\GoogleAds\V23\Resources\Asset; 
 use Google\Ads\GoogleAds\V23\Resources\AssetGroup; 
 use Google\Ads\GoogleAds\V23\Resources\AssetGroupAsset; 
 use Google\Ads\GoogleAds\V23\Resources\AssetSet; 
 use Google\Ads\GoogleAds\V23\Resources\AssetSetAsset; 
 use Google\Ads\GoogleAds\V23\Resources\Campaign; 
 use Google\Ads\GoogleAds\V23\Resources\CampaignBudget; 
 use Google\Ads\GoogleAds\V23\Services\AssetGroupAssetOperation; 
 use Google\Ads\GoogleAds\V23\Services\AssetGroupOperation; 
 use Google\Ads\GoogleAds\V23\Services\AssetOperation; 
 use Google\Ads\GoogleAds\V23\Services\AssetSetAssetOperation; 
 use Google\Ads\GoogleAds\V23\Services\AssetSetOperation; 
 use Google\Ads\GoogleAds\V23\Services\CampaignBudgetOperation; 
 use Google\Ads\GoogleAds\V23\Services\CampaignOperation; 
 use Google\Ads\GoogleAds\V23\Services\HotelAssetSuggestion; 
 use Google\Ads\GoogleAds\V23\Services\HotelImageAsset; 
 use Google\Ads\GoogleAds\V23\Services\HotelTextAsset; 
 use Google\Ads\GoogleAds\V23\Services\MutateAssetSetsRequest; 
 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\SuggestTravelAssetsRequest; 
 use Google\ApiCore\ApiException; 
 use Google\ApiCore\Serializer; 
 /** 
 * This example shows how to create a Performance Max for travel goals campaign. It also uses 
 * TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case 
 * there are not enough assets for the asset group (required by Performance Max), this example will 
 * create more assets to fulfill the requirements. 
 * 
 * For more information about Performance Max campaigns, see 
 * https://developers.google.com/google-ads/api/docs/performance-max/overview. 
 * 
 * Prerequisites: 
 * - You must have at least one conversion action in the account. For more about conversion actions, 
 * see 
 * https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
 * 
 * Notes: 
 * - This example uses the default customer conversion goals. For an example of setting 
 *   campaign-specific conversion goals, see ShoppingAds/AddPerformanceMaxRetailCampaign.php. 
 * - To learn how to create asset group signals, see 
 *   AdvancedOperations/AddPerformanceMaxCampaign.php. 
 */ 
 class AddPerformanceMaxForTravelGoalsCampaign 
 { 
 private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE'; 
 // Sets a place ID that uniquely identifies a place in the Google Places database. 
 // See https://developers.google.com/places/web-service/place-id to learn more. 
 // The provided place ID must belong to a hotel property. 
 private const PLACE_ID = 'INSERT_PLACE_ID_HERE'; 
 // Minimum requirements of assets required in a Performance Max asset group. 
 // See https://developers.google.com/google-ads/api/docs/performance-max/assets for details. 
 private const MIN_REQUIRED_TEXT_ASSET_COUNTS = [ 
 AssetFieldType::HEADLINE => 3, 
 AssetFieldType::LONG_HEADLINE => 1, 
 AssetFieldType::DESCRIPTION => 2, 
 AssetFieldType::BUSINESS_NAME => 1 
 ]; 
 private const MIN_REQUIRED_IMAGE_ASSET_COUNTS = [ 
 AssetFieldType::MARKETING_IMAGE => 1, 
 AssetFieldType::SQUARE_MARKETING_IMAGE => 1, 
 AssetFieldType::LOGO => 1 
 ]; 
 // Texts and URLs used to create text and image assets when the TravelAssetSuggestionService 
 // doesn't return enough assets required for creating an asset group. 
 private const DEFAULT_TEXT_ASSETS_INFO = [ 
 AssetFieldType::HEADLINE => ['Hotel', 'Travel Reviews', 'Book travel'], 
 AssetFieldType::LONG_HEADLINE => ['Travel the World'], 
 AssetFieldType::DESCRIPTION => [ 
 'Great deal for your beloved hotel', 
 'Best rate guaranteed' 
 ], 
 AssetFieldType::BUSINESS_NAME => ['Interplanetary Cruises'] 
 ]; 
 private const DEFAULT_IMAGE_ASSETS_INFO = [ 
 AssetFieldType::MARKETING_IMAGE => ['https://gaagl.page.link/Eit5'], 
 AssetFieldType::SQUARE_MARKETING_IMAGE => ['https://gaagl.page.link/bjYi'], 
 AssetFieldType::LOGO => ['https://gaagl.page.link/bjYi'] 
 ]; 
 // 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. 
 private const ASSET_TEMPORARY_ID = -1; 
 private const BUDGET_TEMPORARY_ID = -2; 
 private const CAMPAIGN_TEMPORARY_ID = -3; 
 private const ASSET_GROUP_TEMPORARY_ID = -4; 
 // There are also entities that will be created in the same request but do not need to be fixed 
 // temporary IDs because they are referenced only once. 
 /** @var int the negative temporary ID used in bulk mutates. */ 
 private static $nextTempId = self::ASSET_GROUP_TEMPORARY_ID - 1; 
 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::PLACE_ID => GetOpt::REQUIRED_ARGUMENT 
 ]); 
 // Generate a refreshable OAuth2 credential for authentication. 
 $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build(); 
 // Construct a Google Ads client configured from a properties file and the 
 // OAuth2 credentials above. 
 $googleAdsClient = (new GoogleAdsClientBuilder()) 
 ->fromFile() 
 ->withOAuth2Credential($oAuth2Credential) 
 ->build(); 
 try { 
 self::runExample( 
 $googleAdsClient, 
 $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID, 
 $options[ArgumentNames::PLACE_ID] ?: self::PLACE_ID 
 ); 
 } 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 string $placeId the place ID for a hotel property asset 
 */ 
 public static function runExample( 
 GoogleAdsClient $googleAdsClient, 
 int $customerId, 
 string $placeId 
 ) { 
 // Gets hotel asset suggestion using the TravelAssetSuggestionService. 
 $hotelAssetSuggestion = 
 self::getHotelAssetSuggestion($googleAdsClient, $customerId, $placeId); 
 // Performance Max campaigns require that repeated assets such as headlines 
 // and descriptions be created before the campaign. 
 // For the list of required assets for a Performance Max campaign, see 
 // https://developers.google.com/google-ads/api/docs/performance-max/assets. 
 // 
 // This step is the same for any types of Performance Max campaigns. 
 // Creates the headlines using the hotel asset suggestion. 
 $headlineAssetResourceNames = self::createMultipleTextAssets( 
 $googleAdsClient, 
 $customerId, 
 AssetFieldType::HEADLINE, 
 $hotelAssetSuggestion 
 ); 
 // Creates the descriptions using the hotel asset suggestion. 
 $descriptionAssetResourceNames = self::createMultipleTextAssets( 
 $googleAdsClient, 
 $customerId, 
 AssetFieldType::DESCRIPTION, 
 $hotelAssetSuggestion 
 ); 
 // Creates a hotel property asset set, which will be used later to link with a newly created 
 // campaign. 
 $hotelPropertyAssetSetResourceName = 
 self::createHotelAssetSet($googleAdsClient, $customerId); 
 // Creates a hotel property asset and link it with the previously created hotel property 
 // asset set. This asset will also be linked to an asset group in the later steps. 
 // In the real-world scenario, you'd need to create many assets for all your hotel 
 // properties. We use one hotel property here for simplicity. 
 // Both asset and asset set need to be created before creating a campaign, so we cannot 
 // bundle them with other mutate operations below. 
 $hotelPropertyAssetResourceName = self::createHotelAsset( 
 $googleAdsClient, 
 $customerId, 
 $placeId, 
 $hotelPropertyAssetSetResourceName 
 ); 
 // It's important to create the below entities in this order because they depend on 
 // each other. 
 // The below methods create and return mutate operations that we later provide to the 
 // GoogleAdsService.Mutate method in order to create the entities in a single request. 
 // Since the entities for a Performance Max campaign are closely tied to one-another, it's 
 // considered a best practice to create them in a single Mutate request so they all complete 
 // successfully or fail entirely, leaving no orphaned entities. See: 
 // https://developers.google.com/google-ads/api/docs/mutating/overview. 
 $operations = []; 
 $operations[] = self::createCampaignBudgetOperation($customerId); 
 $operations[] = 
 self::createCampaignOperation($customerId, $hotelPropertyAssetSetResourceName); 
 $operations = array_merge($operations, self::createAssetGroupOperations( 
 $customerId, 
 $hotelPropertyAssetResourceName, 
 $headlineAssetResourceNames, 
 $descriptionAssetResourceNames, 
 $hotelAssetSuggestion 
 )); 
 // Issues a mutate request to create everything and prints the results. 
 $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); 
 $response = $googleAdsServiceClient->mutate( 
 MutateGoogleAdsRequest::build($customerId, $operations) 
 ); 
 print "Created the following entities for a campaign budget, a campaign, and an asset group" 
 . " for Performance Max for travel goals:" . PHP_EOL; 
 self::printResponseDetails($response); 
 } 
 /** 
 * Returns hotel asset suggestion obtained from TravelAssetsSuggestionService. 
 * 
 * @param GoogleAdsClient $googleAdsClient the Google Ads API client 
 * @param int $customerId the customer ID 
 * @param string $placeId the place ID of the hotel property you want to get its suggested 
 *     assets 
 * @return HotelAssetSuggestion a hotel asset suggestion 
 */ 
 private static function getHotelAssetSuggestion( 
 GoogleAdsClient $googleAdsClient, 
 int $customerId, 
 string $placeId 
 ): HotelAssetSuggestion { 
 // Send a request to suggest assets to be created as an asset group for the Performance Max 
 // for travel goals campaign. 
 $travelAssetSuggestionServiceClient = 
 $googleAdsClient->getTravelAssetSuggestionServiceClient(); 
 // Uses 'en-US' as an example. It can be any language specifications in BCP 47 format. 
 $request = SuggestTravelAssetsRequest::build($customerId, 'en-US'); 
 // The service accepts several place IDs. We use only one here for demonstration. 
 $request->setPlaceIds([$placeId]); 
 $response = $travelAssetSuggestionServiceClient->suggestTravelAssets($request); 
 printf("Fetched a hotel asset suggestion for the place ID '%s'.%s", $placeId, PHP_EOL); 
 return $response->getHotelAssetSuggestions()[0]; 
 } 
 /** 
 * Creates multiple text assets and returns the list of resource names. The hotel asset 
 * suggestion is used to create a text asset first. If the number of created text assets is 
 * still fewer than the minimum required number of assets of the specified asset field type, 
 * adds more text assets to fulfill the requirement. 
 * 
 * @param GoogleAdsClient $googleAdsClient the Google Ads API client 
 * @param int $customerId the customer ID 
 * @param int $assetFieldType the asset field type that this text assets will be created for 
 * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion 
 * @return string[] a list of asset resource names 
 */ 
 private static function createMultipleTextAssets( 
 GoogleAdsClient $googleAdsClient, 
 int $customerId, 
 int $assetFieldType, 
 HotelAssetSuggestion $hotelAssetSuggestion 
 ): array { 
 // We use the GoogleAdService to create multiple text assets in a single request. 
 // First, adds all the text assets of the specified asset field type. 
 $operations = []; 
 $numOperationsAdded = 0; 
 if ($hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS) { 
 foreach ($hotelAssetSuggestion->getTextAssets() as $textAsset) { 
 /** @var HotelTextAsset $textAsset */ 
 if ($textAsset->getAssetFieldType() !== $assetFieldType) { 
 continue; 
 } 
 $operations[] = new MutateOperation([ 
 'asset_operation' => new AssetOperation([ 
 'create' => new Asset([ 
 'text_asset' => new TextAsset(['text' => $textAsset->getText()]) 
 ]) 
 ]) 
 ]); 
 $numOperationsAdded++; 
 } 
 } 
 // If the added assets are still less than the minimum required assets for the asset field 
 // type, add more text assets using the default texts. 
 if ($numOperationsAdded < self::MIN_REQUIRED_TEXT_ASSET_COUNTS[$assetFieldType]) { 
 for ( 
 $i = 0; 
 $i < self::MIN_REQUIRED_TEXT_ASSET_COUNTS[$assetFieldType] - $numOperationsAdded; 
 $i++ 
 ) { 
 // Creates a mutate operation for a text asset. 
 $operations[] = new MutateOperation([ 
 'asset_operation' => new AssetOperation([ 
 'create' => new Asset([ 
 'text_asset' => new TextAsset([ 
 'text' => self::DEFAULT_TEXT_ASSETS_INFO[$assetFieldType][$i] 
 ]) 
 ]) 
 ]) 
 ]); 
 } 
 } 
 // Issues a mutate request to add all assets. 
 $googleAdsService = $googleAdsClient->getGoogleAdsServiceClient(); 
 /** @var MutateGoogleAdsResponse $mutateGoogleAdsResponse */ 
 $mutateGoogleAdsResponse = 
 $googleAdsService->mutate(MutateGoogleAdsRequest::build($customerId, $operations)); 
 $assetResourceNames = []; 
 foreach ($mutateGoogleAdsResponse->getMutateOperationResponses() as $response) { 
 /** @var MutateOperationResponse $response */ 
 $assetResourceNames[] = $response->getAssetResult()->getResourceName(); 
 } 
 printf( 
 "The following assets are created for the asset field type '%s':%s", 
 AssetFieldType::name($assetFieldType), 
 PHP_EOL 
 ); 
 self::printResponseDetails($mutateGoogleAdsResponse); 
 return $assetResourceNames; 
 } 
 /** 
 * Creates a hotel property asset set. 
 * 
 * @param GoogleAdsClient $googleAdsClient the Google Ads API client 
 * @param int $customerId the customer ID 
 * @return string the created hotel property asset set resource name 
 */ 
 private static function createHotelAssetSet( 
 GoogleAdsClient $googleAdsClient, 
 int $customerId 
 ): string { 
 // Creates an asset set operation for a hotel property asset set. 
 $assetSetOperation = new AssetSetOperation([ 
 // Creates a hotel property asset set. 
 'create' => new AssetSet([ 
 'name' => 'My Hotel propery asset set #' . Helper::getPrintableDatetime(), 
 'type' => AssetSetType::HOTEL_PROPERTY 
 ]) 
 ]); 
 // Issues a mutate request to add a hotel asset set and prints its information. 
 $assetSetServiceClient = $googleAdsClient->getAssetSetServiceClient(); 
 $response = $assetSetServiceClient->mutateAssetSets( 
 MutateAssetSetsRequest::build($customerId, [$assetSetOperation]) 
 ); 
 $assetSetResourceName = $response->getResults()[0]->getResourceName(); 
 printf("Created an asset set with resource name: '%s'.%s", $assetSetResourceName, PHP_EOL); 
 return $assetSetResourceName; 
 } 
 /** 
 * Creates a hotel property asset using the specified place ID. The place ID must belong to 
 * a hotel property. Then, links it to the specified asset set. 
 * 
 * See https://developers.google.com/places/web-service/place-id to search for a hotel place ID. 
 * 
 * @param GoogleAdsClient $googleAdsClient the Google Ads API client 
 * @param int $customerId the customer ID 
 * @param string $placeId the place ID for a hotel 
 * @param string $assetSetResourceName the asset set resource name 
 * @return string the created hotel property asset resource name 
 */ 
 private static function createHotelAsset( 
 GoogleAdsClient $googleAdsClient, 
 int $customerId, 
 string $placeId, 
 string $assetSetResourceName 
 ): string { 
 // We use the GoogleAdService to create an asset and asset set asset in a single 
 // request. 
 $operations = []; 
 $assetResourceName = 
 ResourceNames::forAsset($customerId, self::ASSET_TEMPORARY_ID); 
 // Creates a mutate operation for a hotel property asset. 
 $operations[] = new MutateOperation([ 
 'asset_operation' => new AssetOperation([ 
 // Creates a hotel property asset. 
 'create' => new Asset([ 
 'resource_name' => $assetResourceName, 
 // Creates a hotel property asset for the place ID. 
 'hotel_property_asset' => new HotelPropertyAsset(['place_id' => $placeId]), 
 ]) 
 ]) 
 ]); 
 // Creates a mutate operation for an asset set asset. 
 $operations[] = new MutateOperation([ 
 'asset_set_asset_operation' => new AssetSetAssetOperation([ 
 // Creates an asset set asset. 
 'create' => new AssetSetAsset([ 
 'asset' => $assetResourceName, 
 'asset_set' => $assetSetResourceName 
 ]) 
 ]) 
 ]); 
 // Issues a mutate request to create all entities. 
 $googleAdsService = $googleAdsClient->getGoogleAdsServiceClient(); 
 /** @var MutateGoogleAdsResponse $mutateGoogleAdsResponse */ 
 $mutateGoogleAdsResponse = 
 $googleAdsService->mutate(MutateGoogleAdsRequest::build($customerId, $operations)); 
 print "Created the following entities for the hotel asset:" . PHP_EOL; 
 self::printResponseDetails($mutateGoogleAdsResponse); 
 // Returns the created asset resource name, which will be used later to create an asset 
 // group. Other resource names are not used later. 
 return $mutateGoogleAdsResponse->getMutateOperationResponses()[0]->getAssetResult() 
 ->getResourceName(); 
 } 
 /** 
 * Creates a mutate operation that creates a new campaign budget. 
 * 
 * A temporary ID will be assigned to this campaign budget so that it can be 
 * referenced by other objects being created in the same mutate request. 
 * 
 * @param int $customerId the customer ID 
 * @return MutateOperation the mutate operation that creates a campaign budget 
 */ 
 private static function createCampaignBudgetOperation(int $customerId): MutateOperation 
 { 
 // Creates a mutate operation that creates a campaign budget. 
 return new MutateOperation([ 
 'campaign_budget_operation' => new CampaignBudgetOperation([ 
 'create' => new CampaignBudget([ 
 // Sets a temporary ID in the budget's resource name so it can be referenced 
 // by the campaign in later steps. 
 'resource_name' => ResourceNames::forCampaignBudget( 
 $customerId, 
 self::BUDGET_TEMPORARY_ID 
 ), 
 'name' => 'Performance Max for travel goals campaign budget #' 
 . Helper::getPrintableDatetime(), 
 // The budget period already defaults to DAILY. 
 'amount_micros' => 50000000, 
 'delivery_method' => BudgetDeliveryMethod::STANDARD, 
 // A Performance Max campaign cannot use a shared campaign budget. 
 'explicitly_shared' => false 
 ]) 
 ]) 
 ]); 
 } 
 /** 
 * Creates a mutate operation that creates a new Performance Max campaign. Links the specified 
 * hotel property asset set to this campaign. 
 * 
 * A temporary ID will be assigned to this campaign so that it can be referenced by other 
 * objects being created in the same mutate request. 
 * 
 * @param int $customerId the customer ID 
 * @param string $hotelPropertyAssetSetResourceName the asset set resource name 
 * @return MutateOperation the mutate operation that creates the campaign 
 */ 
 private static function createCampaignOperation( 
 int $customerId, 
 string $hotelPropertyAssetSetResourceName 
 ): MutateOperation { 
 // Creates a mutate operation that creates a campaign. 
 return new MutateOperation([ 
 'campaign_operation' => new CampaignOperation([ 
 'create' => new Campaign([ 
 'name' => 'Performance Max for travel goals campaign #' 
 . Helper::getPrintableDatetime(), 
 // Assigns the resource name with a temporary ID. 
 'resource_name' => ResourceNames::forCampaign( 
 $customerId, 
 self::CAMPAIGN_TEMPORARY_ID 
 ), 
 // Sets the budget using the given budget resource name. 
 'campaign_budget' => ResourceNames::forCampaignBudget( 
 $customerId, 
 self::BUDGET_TEMPORARY_ID 
 ), 
 // The campaign is the only entity in the mutate request that should have its 
 // status set. 
 // Recommendation: Set the campaign to PAUSED when creating it to prevent 
 // the ads from immediately serving. 
 'status' => CampaignStatus::PAUSED, 
 // Performance Max campaigns have an advertising_channel_type of 
 // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. 
 'advertising_channel_type' => AdvertisingChannelType::PERFORMANCE_MAX, 
 // Declare whether or not this campaign serves political ads targeting the EU. 
 'contains_eu_political_advertising' = 
> EuPoliticalAdvertisingStatus::DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING, 
 // To create a Performance Max for travel goals campaign, you need to set 
 // `hotel_property_asset_set`. 
 'hotel_property_asset_set' => $hotelPropertyAssetSetResourceName, 
 // Bidding strategy must be set directly on the campaign. 
 // Setting a portfolio bidding strategy by resource name is not supported. 
 // Max Conversion and Maximize Conversion Value are the only strategies 
 // supported for Performance Max campaigns. 
 // An optional ROAS (Return on Advertising Spend) can be set for 
 // maximize_conversion_value. The ROAS value must be specified as a ratio in 
 // the API. It is calculated by dividing "total value" by "total spend". 
 // For more information on Maximize Conversion Value, see the support 
 // article: https://support.google.com/google-ads/answer/7684216. 
 // A target_roas of 3.5 corresponds to a 350% return on ad spend. 
 'maximize_conversion_value' => new MaximizeConversionValue([ 
 'target_roas' => 3.5 
 ]) 
 ]) 
 ]) 
 ]); 
 } 
 /** 
 * Creates a list of mutate operations that create a new asset group, composed of suggested 
 * assets. In case the number of suggested assets is not enough for the requirements, it'll 
 * create more assets to meet the requirement. 
 * 
 * For the list of required assets for a Performance Max campaign, see 
 * https://developers.google.com/google-ads/api/docs/performance-max/assets. 
 * 
 * @param int $customerId the customer ID 
 * @param string $hotelPropertyAssetResourceName the hotel property asset resource name that 
 *     will be used to create an asset group 
 * @param string[] $headlineAssetResourceNames a list of headline resource names 
 * @param string[] $descriptionAssetResourceNames a list of description resource names 
 * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion 
 * @return MutateOperation[] a list of mutate operations that create the asset group 
 */ 
 private static function createAssetGroupOperations( 
 int $customerId, 
 string $hotelPropertyAssetResourceName, 
 array $headlineAssetResourceNames, 
 array $descriptionAssetResourceNames, 
 HotelAssetSuggestion $hotelAssetSuggestion 
 ): array { 
 $operations = []; 
 // Creates a new mutate operation that creates an asset group using suggested information 
 // when available. 
 $assetGroupName = $hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS 
 ? $hotelAssetSuggestion->getHotelName() 
 : 'Performance Max for travel goals asset group #' . Helper::getPrintableDatetime(); 
 $assetGroupFinalUrls = 
 $hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS 
 ? [$hotelAssetSuggestion->getFinalUrl()] : ['http://www.example.com']; 
 $assetGroupResourceName = 
 ResourceNames::forAssetGroup($customerId, self::ASSET_GROUP_TEMPORARY_ID); 
 $operations[] = new MutateOperation([ 
 'asset_group_operation' => new AssetGroupOperation([ 
 'create' => new AssetGroup([ 
 'resource_name' => $assetGroupResourceName, 
 'name' => $assetGroupName, 
 'campaign' => ResourceNames::forCampaign( 
 $customerId, 
 self::CAMPAIGN_TEMPORARY_ID 
 ), 
 'final_urls' => $assetGroupFinalUrls, 
 'status' => AssetGroupStatus::PAUSED 
 ]) 
 ]) 
 ]); 
 // An asset group is linked to an asset by creating a new asset group asset 
 // and providing: 
 // -  the resource name of the asset group 
 // -  the resource name of the asset 
 // -  the field_type of the asset in this asset group 
 // 
 // To learn more about asset groups, see 
 // https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. 
 // Headline and description assets were created at the first step of this example. So, we 
 // just need to link them with the created asset group. 
 // Links the headline assets to the asset group. 
 foreach ($headlineAssetResourceNames as $resourceName) { 
 $operations[] = new MutateOperation([ 
 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 
 'create' => new AssetGroupAsset([ 
 'asset' => $resourceName, 
 'asset_group' => $assetGroupResourceName, 
 'field_type' => AssetFieldType::HEADLINE 
 ]) 
 ]) 
 ]); 
 } 
 // Links the description assets to the asset group. 
 foreach ($descriptionAssetResourceNames as $resourceName) { 
 $operations[] = new MutateOperation([ 
 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 
 'create' => new AssetGroupAsset([ 
 'asset' => $resourceName, 
 'asset_group' => $assetGroupResourceName, 
 'field_type' => AssetFieldType::DESCRIPTION 
 ]) 
 ]) 
 ]); 
 } 
 // Link the previously created hotel property asset to the asset group. In the real-world 
 // scenario, you'd need to do this step several times for each hotel property asset. 
 $operations[] = new MutateOperation([ 
 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 
 'create' => new AssetGroupAsset([ 
 'asset' => $hotelPropertyAssetResourceName, 
 'asset_group' => $assetGroupResourceName, 
 'field_type' => AssetFieldType::HOTEL_PROPERTY 
 ]) 
 ]) 
 ]); 
 // Creates the rest of required text assets and link them to the asset group. 
 $operations = array_merge( 
 $operations, 
 self::createTextAssetsForAssetGroup($customerId, $hotelAssetSuggestion) 
 ); 
 // Creates the image assets and link them to the asset group. Some optional image assets 
 // suggested by the TravelAssetSuggestionService might be created too. 
 $operations = array_merge( 
 $operations, 
 self::createImageAssetsForAssetGroup($customerId, $hotelAssetSuggestion) 
 ); 
 if ($hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS) { 
 // Creates a new mutate operation for a suggested call-to-action asset and link it 
 // to the asset group. 
 $operations[] = new MutateOperation([ 
 'asset_operation' => new AssetOperation([ 
 'create' => new Asset([ 
 'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId), 
 'name' => 'Suggested call-to-action asset #' 
 . Helper::getShortPrintableDatetime(), 
 'call_to_action_asset' => new CallToActionAsset([ 
 'call_to_action' => $hotelAssetSuggestion->getCallToAction() 
 ]) 
 ]) 
 ]) 
 ]); 
 $operations[] = new MutateOperation([ 
 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 
 'create' => new AssetGroupAsset([ 
 'asset' => ResourceNames::forAsset($customerId, self::$nextTempId), 
 'asset_group' => $assetGroupResourceName, 
 'field_type' => AssetFieldType::CALL_TO_ACTION_SELECTION 
 ]) 
 ]) 
 ]); 
 self::$nextTempId--; 
 } 
 return $operations; 
 } 
 /** 
 * Creates text assets required for an asset group using the suggested hotel text assets. It 
 * adds more text assets to fulfill the requirements if the suggested hotel text assets are not 
 * enough. 
 * 
 * @param int $customerId the customer ID 
 * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion 
 * @return MutateOperation[] a list of mutate operations that create text assets 
 */ 
 private static function createTextAssetsForAssetGroup( 
 int $customerId, 
 HotelAssetSuggestion $hotelAssetSuggestion 
 ): array { 
 $operations = []; 
 // Creates mutate operations for the suggested text assets except for headlines and 
 // descriptions, which were created previously. 
 $requiredTextAssetCounts = 
 array_fill_keys(array_keys(self::MIN_REQUIRED_TEXT_ASSET_COUNTS), 0); 
 if ($hotelAssetSuggestion->getStatus() === HotelAssetSuggestionStatus::SUCCESS) { 
 foreach ($hotelAssetSuggestion->getTextAssets() as $textAsset) { 
 /** @var HotelTextAsset $textAsset */ 
 if ( 
 $textAsset->getAssetFieldType() === AssetFieldType::HEADLINE 
 || $textAsset->getAssetFieldType() === AssetFieldType::DESCRIPTION 
 ) { 
 // Headlines and descriptions were already created at the first step of this 
 // code example. 
 continue; 
 } 
 printf( 
 "A text asset with text '%s' is suggested for the asset field type '%s'.%s", 
 $textAsset->getText(), 
 AssetFieldType::name($textAsset->getAssetFieldType()), 
 PHP_EOL 
 ); 
 $operations = array_merge( 
 $operations, 
 self::createTextAssetAndAssetGroupAssetOperations( 
 $customerId, 
 $textAsset->getText(), 
 $textAsset->getAssetFieldType() 
 ) 
 ); 
 $requiredTextAssetCounts[$textAsset->getAssetFieldType()]++; 
 } 
 } 
 // Adds more text assets to fulfill the requirements. 
 foreach (self::MIN_REQUIRED_TEXT_ASSET_COUNTS as $assetFieldType => $minCount) { 
 if ( 
 $assetFieldType === AssetFieldType::HEADLINE 
 || $assetFieldType === AssetFieldType::DESCRIPTION 
 ) { 
 // Headlines and descriptions were already created at the first step of this 
 // code example. 
 continue; 
 } 
 for ($i = 0; $i < $minCount - $requiredTextAssetCounts[$assetFieldType]; $i++) { 
 printf( 
 "A default text '%s' is used to create a text asset for the asset" 
 . " field type '%s'.%s", 
 self::DEFAULT_TEXT_ASSETS_INFO[$assetFieldType][$i], 
 AssetFieldType::name($assetFieldType), 
 PHP_EOL 
 ); 
 $operations = array_merge( 
 $operations, 
 self::createTextAssetAndAssetGroupAssetOperations( 
 $customerId, 
 self::DEFAULT_TEXT_ASSETS_INFO[$assetFieldType][$i], 
 $assetFieldType 
 ) 
 ); 
 } 
 } 
 return $operations; 
 } 
 /** 
 * Creates image assets required for an asset group using the suggested hotel image assets. It 
 * adds more image assets to fulfill the requirements if the suggested hotel image assets are 
 * not enough. 
 * 
 * @param int $customerId the customer ID 
 * @param HotelAssetSuggestion $hotelAssetSuggestion the hotel asset suggestion 
 * @return MutateOperation[] a list of mutate operations that create image assets 
 */ 
 private static function createImageAssetsForAssetGroup( 
 int $customerId, 
 HotelAssetSuggestion $hotelAssetSuggestion 
 ): array { 
 $operations = []; 
 // Creates mutate operations for the suggested image assets. 
 $requiredImageAssetCounts = 
 array_fill_keys(array_keys(self::MIN_REQUIRED_IMAGE_ASSET_COUNTS), 0); 
 foreach ($hotelAssetSuggestion->getImageAssets() as $imageAsset) { 
 /** @var HotelImageAsset $imageAsset */ 
 printf( 
 "An image asset with URL '%s' is suggested for the asset field type '%s'.%s", 
 $imageAsset->getUri(), 
 AssetFieldType::name($imageAsset->getAssetFieldType()), 
 PHP_EOL 
 ); 
 $operations = array_merge( 
 $operations, 
 self::createImageAssetAndAssetGroupAssetOperations( 
 $customerId, 
 $imageAsset->getUri(), 
 $imageAsset->getAssetFieldType(), 
 'Suggested image asset #' . Helper::getShortPrintableDatetime() 
 ) 
 ); 
 // Keeps track of only required image assets. The service may sometimes suggest 
 // optional image assets. 
 if (array_key_exists($imageAsset->getAssetFieldType(), $requiredImageAssetCounts)) { 
 $requiredImageAssetCounts[$imageAsset->getAssetFieldType()]++; 
 } 
 } 
 // Adds more image assets to fulfill the requirements. 
 foreach (self::MIN_REQUIRED_IMAGE_ASSET_COUNTS as $assetFieldType => $minCount) { 
 for ($i = 0; $i < $minCount - $requiredImageAssetCounts[$assetFieldType]; $i++) { 
 printf( 
 "A default image URL '%s' is used to create an image asset for the" 
 . " asset field type '%s'.%s", 
 self::DEFAULT_IMAGE_ASSETS_INFO[$assetFieldType][$i], 
 AssetFieldType::name($assetFieldType), 
 PHP_EOL 
 ); 
 $operations = array_merge( 
 $operations, 
 self::createImageAssetAndAssetGroupAssetOperations( 
 $customerId, 
 self::DEFAULT_IMAGE_ASSETS_INFO[$assetFieldType][$i], 
 $assetFieldType, 
 strtolower(AssetFieldType::name($assetFieldType)) 
 . Helper::getShortPrintableDatetime() 
 ) 
 ); 
 } 
 } 
 return $operations; 
 } 
 /** 
 * Creates a list of mutate operations that create a new linked text asset. 
 * 
 * @param int $customerId the customer ID 
 * @param string $text the text of the asset to be created 
 * @param int $fieldType the field type of the new asset in the asset group asset 
 * @return MutateOperation[] a list of mutate operations that create a new linked text asset 
 */ 
 private static function createTextAssetAndAssetGroupAssetOperations( 
 int $customerId, 
 string $text, 
 int $fieldType 
 ): array { 
 $operations = []; 
 // Creates a new mutate operation that creates a text asset. 
 $operations[] = new MutateOperation([ 
 'asset_operation' => new AssetOperation([ 
 'create' => new Asset([ 
 'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId), 
 'text_asset' => new TextAsset(['text' => $text]) 
 ]) 
 ]) 
 ]); 
 // Creates an asset group asset to link the asset to the asset group. 
 $operations[] = new MutateOperation([ 
 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 
 'create' => new AssetGroupAsset([ 
 'asset' => ResourceNames::forAsset($customerId, self::$nextTempId), 
 'asset_group' => ResourceNames::forAssetGroup( 
 $customerId, 
 self::ASSET_GROUP_TEMPORARY_ID 
 ), 
 'field_type' => $fieldType 
 ]) 
 ]) 
 ]); 
 self::$nextTempId--; 
 return $operations; 
 } 
 /** 
 * Creates a list of mutate operations that create a new linked image asset. 
 * 
 * @param int $customerId the customer ID 
 * @param string $url the URL of the image to be retrieved and put into an asset 
 * @param int $fieldType the field type of the new asset in the asset group asset 
 * @param string $assetName the asset name 
 * @return MutateOperation[] a list of mutate operations that create a new linked image asset 
 */ 
 private static function createImageAssetAndAssetGroupAssetOperations( 
 int $customerId, 
 string $url, 
 int $fieldType, 
 string $assetName 
 ): array { 
 $operations = []; 
 // Creates a new mutate operation that creates an image asset. 
 $operations[] = new MutateOperation([ 
 'asset_operation' => new AssetOperation([ 
 'create' => new Asset([ 
 'resource_name' => ResourceNames::forAsset($customerId, self::$nextTempId), 
 // Provide a unique friendly name to identify your asset. 
 // When there is an existing image asset with the same content but a different 
 // name, the new name will be dropped silently. 
 'name' => $assetName, 
 'image_asset' => new ImageAsset(['data' => file_get_contents($url)]) 
 ]) 
 ]) 
 ]); 
 // Creates an asset group asset to link the asset to the asset group. 
 $operations[] = new MutateOperation([ 
 'asset_group_asset_operation' => new AssetGroupAssetOperation([ 
 'create' => new AssetGroupAsset([ 
 'asset' => ResourceNames::forAsset($customerId, self::$nextTempId), 
 'asset_group' => ResourceNames::forAssetGroup( 
 $customerId, 
 self::ASSET_GROUP_TEMPORARY_ID 
 ), 
 'field_type' => $fieldType 
 ]) 
 ]) 
 ]); 
 self::$nextTempId--; 
 return $operations; 
 } 
 /** 
 * 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. 
 * 
 * @param MutateGoogleAdsResponse $mutateGoogleAdsResponse the mutate Google Ads response 
 */ 
 private static function printResponseDetails( 
 MutateGoogleAdsResponse $mutateGoogleAdsResponse 
 ): void { 
 foreach ($mutateGoogleAdsResponse->getMutateOperationResponses() as $response) { 
 /** @var MutateOperationResponse $response */ 
 $getter = Serializer::getGetter($response->getResponse()); 
 printf( 
 "Created a(n) %s with '%s'.%s", 
 preg_replace( 
 '/Result$/', 
 '', 
 ucfirst(Serializer::toCamelCase($response->getResponse())) 
 ), 
 $response->$getter()->getResourceName(), 
 PHP_EOL 
 ); 
 } 
 } 
 } 
 AddPerformanceMaxForTravelGoalsCampaign::main(); 
  
  

Python

 #!/usr/bin/env python 
 # Copyright 2023 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 create a Performance Max for travel goals campaign. 
 It also uses TravelAssetSuggestionService to fetch suggested assets for creating 
 an asset group. In case there are not enough assets for the asset group 
 (required by Performance Max), this example will create more assets to fulfill 
 the requirements. 
 For more information about Performance Max campaigns, see 
 https://developers.google.com/google-ads/api/docs/performance-max/overview. 
 Prerequisites: 
 - You must have at least one conversion action in the account. For more about 
 conversion actions, see 
 https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
 Notes: 
 - This example uses the default customer conversion goals. For an example of 
 setting campaign-specific conversion goals, see 
 shopping_ads/add_performance_max_retail_campaign.py. 
 - To learn how to create asset group signals, see 
 advanced_operations/add_performance_max_campaign.py. 
 """ 
 import 
  
 argparse 
 import 
  
 sys 
 from 
  
 typing 
  
 import 
 Dict 
 , 
 List 
 from 
  
 examples.utils.example_helpers 
  
 import 
 ( 
 get_printable_datetime 
 , 
 get_image_bytes_from_url 
 , 
 ) 
 from 
  
 google.ads.googleads.client 
  
 import 
 GoogleAdsClient 
 from 
  
 google.ads.googleads.errors 
  
 import 
 GoogleAdsException 
 from 
  
 google.ads.googleads.v23.enums.types.asset_field_type 
  
 import 
 ( 
 AssetFieldTypeEnum 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.enums.types.hotel_asset_suggestion_status 
  
 import 
 ( 
 HotelAssetSuggestionStatusEnum 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.resources.types 
  
 import 
 CampaignBudget 
 from 
  
 google.ads.googleads.v23.resources.types.campaign 
  
 import 
 Campaign 
 from 
  
 google.ads.googleads.v23.resources.types.asset 
  
 import 
 Asset 
 from 
  
 google.ads.googleads.v23.resources.types.asset_group 
  
 import 
 AssetGroup 
 from 
  
 google.ads.googleads.v23.resources.types.asset_group_asset 
  
 import 
 ( 
 AssetGroupAsset 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.services.google_ads_service 
  
 import 
 ( 
 GoogleAdsServiceClient 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.types.google_ads_service 
  
 import 
 ( 
 MutateGoogleAdsResponse 
 , 
 MutateOperation 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.types.google_ads_service 
  
 import 
 ( 
 MutateOperationResponse 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.resources.types.asset_set 
  
 import 
 AssetSet 
 from 
  
 google.ads.googleads.v23.resources.types.asset_set_asset 
  
 import 
 ( 
 AssetSetAsset 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.services.asset_set_service 
  
 import 
 ( 
 AssetSetServiceClient 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.types.asset_set_service 
  
 import 
 ( 
 AssetSetOperation 
 , 
 MutateAssetSetsResponse 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.services.travel_asset_suggestion_service 
  
 import 
 ( 
 TravelAssetSuggestionServiceClient 
 , 
 ) 
 from 
  
 google.ads.googleads.v23.services.types.travel_asset_suggestion_service 
  
 import 
 ( 
 HotelAssetSuggestion 
 , 
 SuggestTravelAssetsRequest 
 , 
 SuggestTravelAssetsResponse 
 , 
 ) 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 : 
 Dict 
 [ 
 str 
 , 
 int 
 ] 
 = 
 { 
 "HEADLINE" 
 : 
 3 
 , 
 "LONG_HEADLINE" 
 : 
 1 
 , 
 "DESCRIPTION" 
 : 
 2 
 , 
 "BUSINESS_NAME" 
 : 
 1 
 , 
 } 
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 : 
 Dict 
 [ 
 str 
 , 
 int 
 ] 
 = 
 { 
 "MARKETING_IMAGE" 
 : 
 1 
 , 
 "SQUARE_MARKETING_IMAGE" 
 : 
 1 
 , 
 "LOGO" 
 : 
 1 
 , 
 } 
 DEFAULT_TEXT_ASSETS_INFO 
 : 
 Dict 
 [ 
 str 
 , 
 List 
 [ 
 str 
 ]] 
 = 
 { 
 "HEADLINE" 
 : 
 [ 
 "Hotel" 
 , 
 "Travel Reviews" 
 , 
 "Book travel" 
 ], 
 "LONG_HEADLINE" 
 : 
 [ 
 "Travel the World" 
 ], 
 "DESCRIPTION" 
 : 
 [ 
 "Great deal for your beloved hotel" 
 , 
 "Best rate guaranteed" 
 , 
 ], 
 "BUSINESS_NAME" 
 : 
 [ 
 "Interplanetary Cruises" 
 ], 
 } 
 DEFAULT_IMAGE_ASSETS_INFO 
 : 
 Dict 
 [ 
 str 
 , 
 List 
 [ 
 str 
 ]] 
 = 
 { 
 "MARKETING_IMAGE" 
 : 
 [ 
 "https://gaagl.page.link/Eit5" 
 ], 
 "SQUARE_MARKETING_IMAGE" 
 : 
 [ 
 "https://gaagl.page.link/bjYi" 
 ], 
 "LOGO" 
 : 
 [ 
 "https://gaagl.page.link/bjYi" 
 ], 
 } 
 # We specify temporary IDs that are specific to a single mutate request. 
 # Temporary IDs are always negative and unique within one mutate request. 
 # For further details, see: 
 # https://developers.google.com/google-ads/api/docs/mutating/best-practices 
 # These temporary IDs are global because they are used throughout the module. 
 ASSET_TEMPORARY_ID 
 : 
 int 
 = 
 - 
 1 
 BUDGET_TEMPORARY_ID 
 : 
 int 
 = 
 - 
 2 
 CAMPAIGN_TEMPORARY_ID 
 : 
 int 
 = 
 - 
 3 
 ASSET_GROUP_TEMPORARY_ID 
 : 
 int 
 = 
 - 
 4 
 # There are also entities that will be created in the same request but do not 
 # need to be fixed temporary IDs because they are referenced only once. 
 next_temp_id 
 : 
 int 
 = 
 ASSET_GROUP_TEMPORARY_ID 
 - 
 1 
 def 
  
 main 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 place_id 
 : 
 str 
 ) 
 - 
> None 
 : 
  
 """The main method that creates all necessary entities for the example. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 place_id: a place ID identifying a place in the Google Places database. 
 """ 
 # Gets hotel asset suggestion using the TravelAssetSuggestionService. 
 hotel_asset_suggestion 
 : 
 HotelAssetSuggestion 
 = 
 get_hotel_asset_suggestion 
 ( 
 client 
 , 
 customer_id 
 , 
 place_id 
 ) 
 # Performance Max campaigns require that repeated assets such as headlines 
 # and descriptions be created before the campaign. For the list of required 
 # assets for a Performance Max campaign, see: 
 # https://developers.google.com/google-ads/api/docs/performance-max/assets. 
 # This step is the same for all types of Performance Max campaigns. 
 # Creates the headlines using the hotel asset suggestion. 
 headline_asset_resource_names 
 : 
 List 
 [ 
 str 
 ] 
 = 
 create_multiple_text_assets 
 ( 
 client 
 , 
 customer_id 
 , 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 . 
 HEADLINE 
 , 
 hotel_asset_suggestion 
 , 
 ) 
 # Creates the descriptions using the hotel asset suggestion. 
 description_asset_resource_names 
 : 
 List 
 [ 
 str 
 ] 
 = 
 create_multiple_text_assets 
 ( 
 client 
 , 
 customer_id 
 , 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 . 
 DESCRIPTION 
 , 
 hotel_asset_suggestion 
 , 
 ) 
 # Creates a hotel property asset set, which will be used later to link with 
 # a newly created campaign. 
 hotel_property_asset_set_resource_name 
 : 
 str 
 = 
 create_hotel_asset_set 
 ( 
 client 
 , 
 customer_id 
 ) 
 # Creates a hotel property asset and links it with the previously created 
 # hotel property asset set. This asset will also be linked to an asset group 
 # in the later steps. In a real-world scenario, you'd need to create assets 
 # for each of your hotel properties. We use one hotel property here for 
 # simplicity. Both asset and asset set need to be created before creating a 
 # campaign, so we cannot bundle them with other mutate operations below. 
 hotel_property_asset_resource_name 
 : 
 str 
 = 
 create_hotel_asset 
 ( 
 client 
 , 
 customer_id 
 , 
 place_id 
 , 
 hotel_property_asset_set_resource_name 
 ) 
 # It's important to create the below entities in this order because they 
 # depend on each other. 
 # The below methods create and return mutate operations that we later 
 # provide to the GoogleAdsService.Mutate method in order to create the 
 # entities in a single request. Since the entities for a Performance Max 
 # campaign are closely tied to one-another, it's considered a best practice 
 # to create them in a single Mutate request so they all complete 
 # successfully or fail entirely, leaving no orphaned entities. See: 
 # https://developers.google.com/google-ads/api/docs/mutating/overview. 
 campaign_budget_operation 
 : 
 MutateOperation 
 = 
 ( 
 create_campaign_budget_operation 
 ( 
 client 
 , 
 customer_id 
 ) 
 ) 
 campaign_operation 
 : 
 MutateOperation 
 = 
 create_campaign_operation 
 ( 
 client 
 , 
 customer_id 
 , 
 hotel_property_asset_set_resource_name 
 ) 
 asset_group_operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 ( 
 create_asset_group_operations 
 ( 
 client 
 , 
 customer_id 
 , 
 hotel_property_asset_resource_name 
 , 
 headline_asset_resource_names 
 , 
 description_asset_resource_names 
 , 
 hotel_asset_suggestion 
 , 
 ) 
 ) 
 # Issues a mutate request to create everything. 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 # The list of operations is a MutableSequence because it is modified by 
 # the `extend` method. 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [ 
 campaign_budget_operation 
 , 
 campaign_operation 
 , 
 * 
 asset_group_operations 
 , 
 ] 
 response 
 : 
 MutateGoogleAdsResponse 
 = 
 googleads_service 
 . 
 mutate 
 ( 
 customer_id 
 = 
 customer_id 
 , 
 mutate_operations 
 = 
 operations 
 , 
 ) 
 print 
 ( 
 "Created the following entities for a campaign budget, a campaign, and " 
 "an asset group for Performance Max for travel goals:" 
 ) 
 print_response_details 
 ( 
 response 
 ) 
 def 
  
 get_hotel_asset_suggestion 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 place_id 
 : 
 str 
 ) 
 - 
> HotelAssetSuggestion 
 : 
  
 """Returns hotel asset suggestion from TravelAssetsSuggestionService. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 place_id: a place ID identifying a place in the Google Places database. 
 Returns: 
 A HotelAssetSuggestion instance. 
 """ 
 request 
 : 
 SuggestTravelAssetsRequest 
 = 
 client 
 . 
 get_type 
 ( 
 "SuggestTravelAssetsRequest" 
 ) 
 request 
 . 
 customer_id 
 = 
 customer_id 
 # Uses 'en-US' as an example. It can be any language specifications in 
 # BCP 47 format. 
 request 
 . 
 language_option 
 = 
 "en-US" 
 # In this example we only use a single place ID for the purpose of 
 # demonstration, but it's possible to append more than one here if needed. 
 request 
 . 
 place_ids 
 . 
 append 
 ( 
 place_id 
 ) 
 travel_asset_suggestion_service 
 : 
 TravelAssetSuggestionServiceClient 
 = 
 ( 
 client 
 . 
 get_service 
 ( 
 "TravelAssetSuggestionService" 
 ) 
 ) 
 response 
 : 
 SuggestTravelAssetsResponse 
 = 
 ( 
 travel_asset_suggestion_service 
 . 
 suggest_travel_assets 
 ( 
 request 
 = 
 request 
 ) 
 ) 
 print 
 ( 
 f 
 "Fetched a hotel asset suggestion for the place ID: ' 
 { 
 place_id 
 } 
 '." 
 ) 
 # Since we sent a single operation in the request, it's guaranteed that 
 # there will only be a single item in the response. 
 return 
 response 
 . 
 hotel_asset_suggestions 
 [ 
 0 
 ] 
 def 
  
 create_multiple_text_assets 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 asset_field_type 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 , 
 hotel_asset_suggestion 
 : 
 HotelAssetSuggestion 
 , 
 ) 
 - 
> List 
 [ 
 str 
 ]: 
  
 """Creates multiple text assets and returns the list of resource names. 
 The hotel asset suggestion is used to create a text asset first. If the 
 number of created text assets is still fewer than the minimum required 
 number of assets of the specified asset field type, adds more text assets to 
 fulfill the requirement. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 asset_field_type: the asset field type enum that the new assets will be 
 created as. 
 hotel_asset_suggestion: the hotel asset suggestion. 
 Returns: 
 a list of asset resource names. 
 """ 
 # We use the GoogleAdService to create multiple text assets in a single 
 # request. First, adds all the text assets of the specified asset field 
 # type. 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [] 
 success_status 
 : 
 ( 
 HotelAssetSuggestionStatusEnum 
 . 
 HotelAssetSuggestionStatus 
 ) 
 = 
 client 
 . 
 enums 
 . 
 HotelAssetSuggestionStatusEnum 
 . 
 SUCCESS 
 if 
 hotel_asset_suggestion 
 . 
 status 
 == 
 success_status 
 : 
 for 
 text_asset 
 in 
 hotel_asset_suggestion 
 . 
 text_assets 
 : 
 # If the suggested text asset is not of the type specified, then 
 # we skip it and move on to the next text asset. 
 if 
 text_asset 
 . 
 asset_field_type 
 != 
 asset_field_type 
 : 
 continue 
 # If the suggested text asset is of the type specified, then we 
 # build a mutate operation that creates a new text asset using 
 # the text from the suggestion. 
 operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset 
 : 
 Asset 
 = 
 operation 
 . 
 asset_operation 
 . 
 create 
 asset 
 . 
 text_asset 
 . 
 text 
 = 
 text_asset 
 . 
 text 
 operations 
 . 
 append 
 ( 
 operation 
 ) 
 # If the current number of operations is still less than the minimum 
 # required assets for the asset field type, add more operations using the 
 # default texts. 
 minimum_required_text_asset_count 
 : 
 int 
 = 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 [ 
 asset_field_type 
 . 
 name 
 ] 
 if 
 len 
 ( 
 operations 
 ) 
< minimum_required_text_asset_count 
 : 
 # Calculate the number of additional operations that need to be created. 
 difference 
 : 
 int 
 = 
 minimum_required_text_asset_count 
 - 
 len 
 ( 
 operations 
 ) 
 # Retrieve the list of default texts for the given asset type. 
 default_texts 
 : 
 List 
 [ 
 str 
 ] 
 = 
 DEFAULT_TEXT_ASSETS_INFO 
 [ 
 asset_field_type 
 . 
 name 
 ] 
 for 
 i 
 in 
 range 
 ( 
 difference 
 ): 
 operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset 
 : 
 Asset 
 = 
 operation 
 . 
 asset_operation 
 . 
 create 
 asset 
 . 
 text_asset 
 . 
 text 
 = 
 default_texts 
 [ 
 i 
 ] 
 operations 
 . 
 append 
 ( 
 operation 
 ) 
 # Issues a mutate request to add all assets. 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 response 
 : 
 MutateGoogleAdsResponse 
 = 
 googleads_service 
 . 
 mutate 
 ( 
 customer_id 
 = 
 customer_id 
 , 
 mutate_operations 
 = 
 operations 
 ) 
 print 
 ( 
 "The following assets were created for the asset field type " 
 f 
 "' 
 { 
 asset_field_type 
 . 
 name 
 } 
 '" 
 ) 
 print_response_details 
 ( 
 response 
 ) 
 return 
 [ 
 result 
 . 
 asset_result 
 . 
 resource_name 
 for 
 result 
 in 
 response 
 . 
 mutate_operation_responses 
 ] 
 def 
  
 create_hotel_asset_set 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 ) 
 - 
> str 
 : 
  
 """Creates a hotel property asset set. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 Returns: 
 the created hotel property asset set's resource name. 
 """ 
 # Creates an asset set operation for a hotel property asset set. 
 operation 
 : 
 AssetSetOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "AssetSetOperation" 
 ) 
 # Creates a hotel property asset set. 
 asset_set 
 : 
 AssetSet 
 = 
 operation 
 . 
 create 
 asset_set 
 . 
 name 
 = 
 f 
 "My hotel property asset set # 
 { 
 get_printable_datetime 
 () 
 } 
 " 
 asset_set 
 . 
 type_ 
 = 
 client 
 . 
 enums 
 . 
 AssetSetTypeEnum 
 . 
 HOTEL_PROPERTY 
 # Issues a mutate request to add a hotel asset set. 
 asset_set_service 
 : 
 AssetSetServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "AssetSetService" 
 ) 
 response 
 : 
 MutateAssetSetsResponse 
 = 
 asset_set_service 
 . 
 mutate_asset_sets 
 ( 
 customer_id 
 = 
 customer_id 
 , 
 operations 
 = 
 [ 
 operation 
 ] 
 ) 
 resource_name 
 : 
 str 
 = 
 response 
 . 
 results 
 [ 
 0 
 ] 
 . 
 resource_name 
 print 
 ( 
 f 
 "Created an asset set with resource name: ' 
 { 
 resource_name 
 } 
 '" 
 ) 
 return 
 resource_name 
 def 
  
 create_hotel_asset 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 place_id 
 : 
 str 
 , 
 asset_set_resource_name 
 : 
 str 
 , 
 ) 
 - 
> str 
 : 
  
 """Creates a hotel property asset using the specified place ID. 
 The place ID must belong to a hotel property. Then, links it to the 
 specified asset set. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 place_id: a place ID identifying a place in the Google Places database. 
 asset_set_resource_name: an asset set resource name 
 Returns: 
 the created hotel property asset's resource name. 
 """ 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 # We use the GoogleAdService to create an asset and asset set asset in a 
 # single request. 
 asset_resource_name 
 : 
 str 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 ASSET_TEMPORARY_ID 
 ) 
 # Creates a mutate operation for a hotel property asset. 
 asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 # Creates a hotel property asset. 
 asset 
 : 
 Asset 
 = 
 asset_mutate_operation 
 . 
 asset_operation 
 . 
 create 
 asset 
 . 
 resource_name 
 = 
 asset_resource_name 
 # Creates a hotel property asset for the place ID. 
 asset 
 . 
 hotel_property_asset 
 . 
 place_id 
 = 
 place_id 
 # Creates a mutate operation for an asset set asset. 
 asset_set_asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 # Creates an asset set asset. 
 asset_set_asset 
 : 
 AssetSetAsset 
 = 
 ( 
 asset_set_asset_mutate_operation 
 . 
 asset_set_asset_operation 
 . 
 create 
 ) 
 asset_set_asset 
 . 
 asset 
 = 
 asset_resource_name 
 asset_set_asset 
 . 
 asset_set 
 = 
 asset_set_resource_name 
 # Issues a mutate request to create all entities. 
 response 
 : 
 MutateGoogleAdsResponse 
 = 
 googleads_service 
 . 
 mutate 
 ( 
 customer_id 
 = 
 customer_id 
 , 
 mutate_operations 
 = 
 [ 
 asset_mutate_operation 
 , 
 asset_set_asset_mutate_operation 
 , 
 ], 
 ) 
 print 
 ( 
 "Created the following entities for the hotel asset:" 
 ) 
 print_response_details 
 ( 
 response 
 ) 
 return 
 response 
 . 
 mutate_operation_responses 
 [ 
 0 
 ] 
 . 
 asset_result 
 . 
 resource_name 
 def 
  
 create_campaign_budget_operation 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 ) 
 - 
> MutateOperation 
 : 
  
 """Creates a MutateOperation that creates a new campaign budget. 
 A temporary ID will be assigned to this campaign budget so that it can be 
 referenced by other objects being created in the same mutate request. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 Returns: A MutateOperation that creates a new campaign budget. 
 """ 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 # Creates a mutate operation that creates a campaign budget. 
 operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 budget 
 : 
 CampaignBudget 
 = 
 operation 
 . 
 campaign_budget_operation 
 . 
 create 
 # Sets a temporary ID in the budget's resource name so it can be referenced 
 # by the campaign in later steps. 
 budget 
 . 
 resource_name 
 = 
 googleads_service 
 . 
 campaign_budget_path 
 ( 
 customer_id 
 , 
 BUDGET_TEMPORARY_ID 
 ) 
 budget 
 . 
 name 
 = 
 ( 
 "Performance Max for travel goals campaign budget " 
 f 
 "# 
 { 
 get_printable_datetime 
 () 
 } 
 " 
 ) 
 # The budget period already defaults to DAILY. 
 budget 
 . 
 amount_micros 
 = 
 50000000 
 budget 
 . 
 delivery_method 
 = 
 client 
 . 
 enums 
 . 
 BudgetDeliveryMethodEnum 
 . 
 STANDARD 
 # A Performance Max campaign cannot use a shared campaign budget. 
 budget 
 . 
 explicitly_shared 
 = 
 False 
 return 
 operation 
 def 
  
 create_campaign_operation 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 hotel_property_asset_set_resource_name 
 : 
 str 
 , 
 ) 
 - 
> MutateOperation 
 : 
  
 """Creates a mutate operation that creates a new Performance Max for travel 
 goals campaign. 
 Links the specified hotel property asset set to this campaign. 
 A temporary ID will be assigned to this campaign budget so that it can be 
 referenced by other objects being created in the same mutate request. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 hotel_property_asset_set_resource_name: the resource name for a hotel 
 property asset set. 
 Returns: 
 a MutateOperation message that creates a new Performance Max campaign. 
 """ 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 # Creates a mutate operation that creates a campaign. 
 operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 campaign 
 : 
 Campaign 
 = 
 operation 
 . 
 campaign_operation 
 . 
 create 
 campaign 
 . 
 name 
 = 
 ( 
 "Performance Max for travel goals campaign " 
 f 
 "# 
 { 
 get_printable_datetime 
 () 
 } 
 " 
 ) 
 # Assigns the resource name with a temporary ID. 
 campaign 
 . 
 resource_name 
 = 
 googleads_service 
 . 
 campaign_path 
 ( 
 customer_id 
 , 
 CAMPAIGN_TEMPORARY_ID 
 ) 
 # Sets the budget using the given budget resource name. 
 campaign 
 . 
 campaign_budget 
 = 
 googleads_service 
 . 
 campaign_budget_path 
 ( 
 customer_id 
 , 
 BUDGET_TEMPORARY_ID 
 ) 
 # The campaign is the only entity in the mutate request that should have its 
 # status set. 
 # Recommendation: Set the campaign to PAUSED when creating it to prevent 
 # the ads from immediately serving. 
 campaign 
 . 
 status 
 = 
 client 
 . 
 enums 
 . 
 CampaignStatusEnum 
 . 
 PAUSED 
 # Performance Max campaigns have an advertising_channel_type of 
 # PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. 
 campaign 
 . 
 advertising_channel_type 
 = 
 ( 
 client 
 . 
 enums 
 . 
 AdvertisingChannelTypeEnum 
 . 
 PERFORMANCE_MAX 
 ) 
 # Declare whether or not this campaign serves political ads targeting the 
 # EU. Valid values are: 
 #   CONTAINS_EU_POLITICAL_ADVERTISING 
 #   DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING 
 campaign 
 . 
 contains_eu_political_advertising 
 = 
 ( 
 client 
 . 
 enums 
 . 
 EuPoliticalAdvertisingStatusEnum 
 . 
 DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING 
 ) 
 # To create a Performance Max for travel goals campaign, you need to set 
 # the `hotel_property_asset_set` field. 
 campaign 
 . 
 hotel_property_asset_set 
 = 
 hotel_property_asset_set_resource_name 
 # Bidding strategy must be set directly on the campaign. 
 # Setting a portfolio bidding strategy by resource name is not supported. 
 # Max Conversion and Maximize Conversion Value are the only strategies 
 # supported for Performance Max campaigns. 
 # An optional ROAS (Return on Advertising Spend) can be set for 
 # maximize_conversion_value. The ROAS value must be specified as a ratio in 
 # the API. It is calculated by dividing "total value" by "total spend". 
 # For more information on Maximize Conversion Value, see the support 
 # article: https://support.google.com/google-ads/answer/7684216. 
 # A target_roas of 3.5 corresponds to a 350% return on ad spend. 
 campaign 
 . 
 maximize_conversion_value 
 . 
 target_roas 
 = 
 3.5 
 return 
 operation 
 def 
  
 create_asset_group_operations 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 hotel_property_asset_resource_name 
 : 
 str 
 , 
 headline_asset_resource_names 
 : 
 List 
 [ 
 str 
 ], 
 description_asset_resource_names 
 : 
 List 
 [ 
 str 
 ], 
 hotel_asset_suggestion 
 : 
 HotelAssetSuggestion 
 , 
 ) 
 - 
> List 
 [ 
 MutateOperation 
 ]: 
  
 """Creates a list of mutate operations that create a new asset group. 
 The asset group is composed of suggested assets. In case the number of 
 suggested assets is not enough for the requirements, it will create more 
 assets to meet the requirement. 
 For the list of required assets for a Performance Max campaign, see 
 https://developers.google.com/google-ads/api/docs/performance-max/assets. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 hotel_property_asset_resource_name: the hotel property asset resource 
 name that will be used to create an asset group. 
 headline_asset_resource_names: a list of headline asset resource names. 
 description_asset_resource_names: a list of description asset resource 
 names. 
 hotel_asset_suggestion: the hotel asset suggestion. 
 Returns: 
 a list of mutate operations that create the asset group. 
 """ 
 global 
 next_temp_id 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [] 
 # Creates a new mutate operation that creates an asset group using suggested 
 # information when available. 
 success_status 
 : 
 ( 
 HotelAssetSuggestionStatusEnum 
 . 
 HotelAssetSuggestionStatus 
 ) 
 = 
 client 
 . 
 enums 
 . 
 HotelAssetSuggestionStatusEnum 
 . 
 SUCCESS 
 asset_group_name 
 : 
 str 
 asset_group_final_urls 
 : 
 List 
 [ 
 str 
 ] 
 if 
 hotel_asset_suggestion 
 . 
 status 
 == 
 success_status 
 : 
 asset_group_name 
 = 
 hotel_asset_suggestion 
 . 
 hotel_name 
 asset_group_final_urls 
 = 
 [ 
 hotel_asset_suggestion 
 . 
 final_url 
 ] 
 else 
 : 
 asset_group_name 
 = 
 ( 
 "Performance Max for travel goals asset group " 
 f 
 "# 
 { 
 get_printable_datetime 
 () 
 } 
 " 
 ) 
 asset_group_final_urls 
 = 
 [ 
 "http://www.example.com" 
 ] 
 asset_group_resource_name 
 : 
 str 
 = 
 googleads_service 
 . 
 asset_group_path 
 ( 
 customer_id 
 , 
 ASSET_GROUP_TEMPORARY_ID 
 ) 
 asset_group_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_group 
 : 
 AssetGroup 
 = 
 ( 
 asset_group_mutate_operation 
 . 
 asset_group_operation 
 . 
 create 
 ) 
 asset_group 
 . 
 resource_name 
 = 
 asset_group_resource_name 
 asset_group 
 . 
 name 
 = 
 asset_group_name 
 asset_group 
 . 
 campaign 
 = 
 googleads_service 
 . 
 campaign_path 
 ( 
 customer_id 
 , 
 CAMPAIGN_TEMPORARY_ID 
 ) 
 asset_group 
 . 
 final_urls 
 = 
 asset_group_final_urls 
 asset_group 
 . 
 status 
 = 
 client 
 . 
 enums 
 . 
 AssetGroupStatusEnum 
 . 
 PAUSED 
 # Append the asset group operation to the list of operations. 
 operations 
 . 
 append 
 ( 
 asset_group_mutate_operation 
 ) 
 # An asset group is linked to an asset by creating a new asset group asset 
 # and providing: 
 # -  the resource name of the asset group 
 # -  the resource name of the asset 
 # -  the field_type of the asset in this asset group 
 # To learn more about asset groups, see 
 # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. 
 # Headline and description assets were created at the first step of this 
 # example. So, we just need to link them with the created asset group. 
 # Links the headline assets to the asset group. 
 for 
 resource_name 
 in 
 headline_asset_resource_names 
 : 
 headline_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_group_asset 
 : 
 AssetGroupAsset 
 = 
 ( 
 headline_operation 
 . 
 asset_group_asset_operation 
 . 
 create 
 ) 
 asset_group_asset 
 . 
 asset 
 = 
 resource_name 
 asset_group_asset 
 . 
 asset_group 
 = 
 asset_group_resource_name 
 asset_group_asset 
 . 
 field_type 
 = 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 . 
 HEADLINE 
 operations 
 . 
 append 
 ( 
 headline_operation 
 ) 
 # Links the description assets to the asset group. 
 for 
 resource_name 
 in 
 description_asset_resource_names 
 : 
 description_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_group_asset_desc 
 : 
 AssetGroupAsset 
 = 
 ( 
 description_operation 
 . 
 asset_group_asset_operation 
 . 
 create 
 ) 
 asset_group_asset_desc 
 . 
 asset 
 = 
 resource_name 
 asset_group_asset_desc 
 . 
 asset_group 
 = 
 asset_group_resource_name 
 asset_group_asset_desc 
 . 
 field_type 
 = 
 ( 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 . 
 DESCRIPTION 
 ) 
 operations 
 . 
 append 
 ( 
 description_operation 
 ) 
 # Link the previously created hotel property asset to the asset group. If 
 # there are multiple assets, these steps to create a new operation need to 
 # be performed for each asset. 
 asset_group_asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_group_asset_hotel 
 : 
 AssetGroupAsset 
 = 
 ( 
 asset_group_asset_mutate_operation 
 . 
 asset_group_asset_operation 
 . 
 create 
 ) 
 asset_group_asset_hotel 
 . 
 asset 
 = 
 hotel_property_asset_resource_name 
 asset_group_asset_hotel 
 . 
 asset_group 
 = 
 asset_group_resource_name 
 asset_group_asset_hotel 
 . 
 field_type 
 = 
 ( 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 . 
 HOTEL_PROPERTY 
 ) 
 operations 
 . 
 append 
 ( 
 asset_group_asset_mutate_operation 
 ) 
 # Creates the rest of required text assets and link them to the asset group. 
 operations 
 . 
 extend 
 ( 
 create_text_assets_for_asset_group 
 ( 
 client 
 , 
 customer_id 
 , 
 hotel_asset_suggestion 
 ) 
 ) 
 # Creates the image assets and link them to the asset group. Some optional 
 # image assets suggested by the TravelAssetSuggestionService might be 
 # created too. 
 operations 
 . 
 extend 
 ( 
 create_image_assets_for_asset_group 
 ( 
 client 
 , 
 customer_id 
 , 
 hotel_asset_suggestion 
 ) 
 ) 
 if 
 hotel_asset_suggestion 
 . 
 status 
 == 
 success_status 
 : 
 # Creates a new mutate operation for a suggested call-to-action asset 
 # and link it to the asset group. 
 asset_mutate_operation_cta 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_cta 
 : 
 Asset 
 = 
 asset_mutate_operation_cta 
 . 
 asset_operation 
 . 
 create 
 asset_cta 
 . 
 resource_name 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 next_temp_id 
 ) 
 asset_cta 
 . 
 name 
 = 
 ( 
 f 
 "Suggested call-to-action asset # 
 { 
 get_printable_datetime 
 () 
 } 
 " 
 ) 
 asset_cta 
 . 
 call_to_action_asset 
 . 
 call_to_action 
 = 
 ( 
 hotel_asset_suggestion 
 . 
 call_to_action 
 ) 
 operations 
 . 
 append 
 ( 
 asset_mutate_operation_cta 
 ) 
 # Creates a new mutate operation for a call-to-action asset group. 
 asset_group_asset_mutate_operation_cta 
 : 
 MutateOperation 
 = 
 ( 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 ) 
 asset_group_asset_cta 
 : 
 AssetGroupAsset 
 = 
 ( 
 asset_group_asset_mutate_operation_cta 
 . 
 asset_group_asset_operation 
 . 
 create 
 ) 
 asset_group_asset_cta 
 . 
 asset 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 next_temp_id 
 ) 
 asset_group_asset_cta 
 . 
 asset_group 
 = 
 asset_group_resource_name 
 asset_group_asset_cta 
 . 
 field_type 
 = 
 ( 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 . 
 CALL_TO_ACTION_SELECTION 
 ) 
 operations 
 . 
 append 
 ( 
 asset_group_asset_mutate_operation_cta 
 ) 
 next_temp_id 
 -= 
 1 
 return 
 operations 
 def 
  
 create_text_assets_for_asset_group 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 hotel_asset_suggestion 
 : 
 HotelAssetSuggestion 
 , 
 ) 
 - 
> List 
 [ 
 MutateOperation 
 ]: 
  
 """Creates text assets for an asset group using the given hotel text assets. 
 It adds more text assets to fulfill the requirements if the suggested hotel 
 text assets are not enough. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 hotel_asset_suggestion: the hotel asset suggestion. 
 Returns: 
 a list of mutate operations that create text assets. 
 """ 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [] 
 # Creates mutate operations for the suggested text assets except for 
 # headlines and descriptions, which were created previously. 
 required_text_asset_counts 
 : 
 Dict 
 [ 
 str 
 , 
 int 
 ] 
 = 
 { 
 key 
 : 
 0 
 for 
 key 
 in 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 . 
 keys 
 () 
 } 
 success_status 
 : 
 ( 
 HotelAssetSuggestionStatusEnum 
 . 
 HotelAssetSuggestionStatus 
 ) 
 = 
 client 
 . 
 enums 
 . 
 HotelAssetSuggestionStatusEnum 
 . 
 SUCCESS 
 if 
 hotel_asset_suggestion 
 . 
 status 
 == 
 success_status 
 : 
 for 
 text_asset 
 in 
 hotel_asset_suggestion 
 . 
 text_assets 
 : 
 text 
 : 
 str 
 = 
 text_asset 
 . 
 text 
 asset_field_type 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 = 
 ( 
 text_asset 
 . 
 asset_field_type 
 ) 
 if 
 asset_field_type 
 . 
 name 
 in 
 ( 
 "HEADLINE" 
 , 
 "DESCRIPTION" 
 ): 
 # Headlines and descriptions were already created at the first 
 # step of this code example 
 continue 
 print 
 ( 
 f 
 "A test asset with text 
 { 
 text 
 } 
 is suggested for the asset " 
 f 
 "field type ` 
 { 
 asset_field_type 
 . 
 name 
 } 
 `" 
 ) 
 operations 
 . 
 extend 
 ( 
 create_text_asset_and_asset_group_asset_operations 
 ( 
 client 
 , 
 customer_id 
 , 
 text 
 , 
 asset_field_type 
 ) 
 ) 
 required_text_asset_counts 
 [ 
 asset_field_type 
 . 
 name 
 ] 
 += 
 1 
 # Adds more text assets to fulfill the requirements. 
 for 
 ( 
 field_type_name 
 , 
 min_count 
 , 
 ) 
 in 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 . 
 items 
 (): 
 if 
 field_type_name 
 in 
 ( 
 "HEADLINE" 
 , 
 "DESCRIPTION" 
 ): 
 # Headlines and descriptions were already created at the first step 
 # of this code example. 
 continue 
 difference 
 : 
 int 
 = 
 ( 
 min_count 
 - 
 required_text_asset_counts 
 [ 
 field_type_name 
 ] 
 ) 
 if 
 difference 
> 0 
 : 
 for 
 i 
 in 
 range 
 ( 
 difference 
 ): 
 default_text 
 : 
 str 
 = 
 DEFAULT_TEXT_ASSETS_INFO 
 [ 
 field_type_name 
 ][ 
 i 
 ] 
 field_type_enum 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 = 
 ( 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 [ 
 field_type_name 
 ] 
 ) 
 print 
 ( 
 f 
 "A default text 
 { 
 default_text 
 } 
 is used to create a " 
 f 
 "text asset for the asset field type 
 { 
 field_type_name 
 } 
 " 
 ) 
 operations 
 . 
 extend 
 ( 
 create_text_asset_and_asset_group_asset_operations 
 ( 
 client 
 , 
 customer_id 
 , 
 default_text 
 , 
 field_type_enum 
 ) 
 ) 
 return 
 operations 
 def 
  
 create_text_asset_and_asset_group_asset_operations 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 text 
 : 
 str 
 , 
 field_type_enum 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 , 
 ) 
 - 
> List 
 [ 
 MutateOperation 
 ]: 
  
 """Creates a list of mutate operations that create a new linked text asset. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 text: the text of an asset to be created. 
 field_type_enum: the field type enum of a new asset in the asset group 
 asset. 
 Returns: 
 a list of mutate operations that create a new linked text asset. 
 """ 
 global 
 next_temp_id 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [] 
 # Creates a new mutate operation that creates a text asset. 
 asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset 
 : 
 Asset 
 = 
 asset_mutate_operation 
 . 
 asset_operation 
 . 
 create 
 asset 
 . 
 resource_name 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 next_temp_id 
 ) 
 asset 
 . 
 text_asset 
 . 
 text 
 = 
 text 
 operations 
 . 
 append 
 ( 
 asset_mutate_operation 
 ) 
 # Creates an asset group asset operation to link the asset to the asset 
 # group. 
 asset_group_asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_group_asset 
 : 
 AssetGroupAsset 
 = 
 ( 
 asset_group_asset_mutate_operation 
 . 
 asset_group_asset_operation 
 . 
 create 
 ) 
 asset_group_asset 
 . 
 asset 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 next_temp_id 
 ) 
 asset_group_asset 
 . 
 asset_group 
 = 
 googleads_service 
 . 
 asset_group_path 
 ( 
 customer_id 
 , 
 ASSET_GROUP_TEMPORARY_ID 
 ) 
 asset_group_asset 
 . 
 field_type 
 = 
 field_type_enum 
 operations 
 . 
 append 
 ( 
 asset_group_asset_mutate_operation 
 ) 
 next_temp_id 
 -= 
 1 
 return 
 operations 
 def 
  
 create_image_assets_for_asset_group 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 hotel_asset_suggestion 
 : 
 HotelAssetSuggestion 
 , 
 ) 
 - 
> List 
 [ 
 MutateOperation 
 ]: 
  
 """Creates image assets for an asset group with the given hotel suggestions. 
 It adds more image assets to fulfill the requirements if the suggested hotel 
 image assets are not enough. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 hotel_asset_suggestion: the hotel asset suggestion. 
 Returns: 
 a list of mutate operations that create image assets. 
 """ 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [] 
 # Creates mutate operations for the suggested image assets. 
 required_image_asset_counts 
 : 
 Dict 
 [ 
 str 
 , 
 int 
 ] 
 = 
 { 
 key 
 : 
 0 
 for 
 key 
 in 
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 . 
 keys 
 () 
 } 
 for 
 image_asset 
 in 
 hotel_asset_suggestion 
 . 
 image_assets 
 : 
 url 
 : 
 str 
 = 
 image_asset 
 . 
 uri 
 field_type_enum 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 = 
 ( 
 image_asset 
 . 
 asset_field_type 
 ) 
 name 
 : 
 str 
 = 
 f 
 "Suggested image asset # 
 { 
 get_printable_datetime 
 () 
 } 
 " 
 print 
 ( 
 f 
 "An image asset with URL ' 
 { 
 url 
 } 
 ' is suggested for the asset field " 
 f 
 "type ' 
 { 
 field_type_enum 
 . 
 name 
 } 
 '" 
 ) 
 operations 
 . 
 extend 
 ( 
 create_image_asset_and_image_asset_group_asset_operations 
 ( 
 client 
 , 
 customer_id 
 , 
 url 
 , 
 field_type_enum 
 , 
 name 
 ) 
 ) 
 # Keeps track of only required image assets. The 
 # TravelAssetSuggestionService may sometimes suggest optional image 
 # assets. 
 if 
 field_type_enum 
 . 
 name 
 in 
 required_image_asset_counts 
 : 
 required_image_asset_counts 
 [ 
 field_type_enum 
 . 
 name 
 ] 
 += 
 1 
 # Adds more image assets to fulfill the requirements. 
 for 
 ( 
 field_type_name 
 , 
 min_count 
 , 
 ) 
 in 
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 . 
 items 
 (): 
 difference 
 : 
 int 
 = 
 ( 
 min_count 
 - 
 required_image_asset_counts 
 [ 
 field_type_name 
 ] 
 ) 
 if 
 difference 
> 0 
 : 
 for 
 i 
 in 
 range 
 ( 
 difference 
 ): 
 default_url 
 : 
 str 
 = 
 DEFAULT_IMAGE_ASSETS_INFO 
 [ 
 field_type_name 
 ][ 
 i 
 ] 
 name 
 = 
 f 
 " 
 { 
 field_type_name 
 . 
 lower 
 () 
 } 
  
 { 
 get_printable_datetime 
 () 
 } 
 " 
 field_type_enum 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 = 
 ( 
 client 
 . 
 enums 
 . 
 AssetFieldTypeEnum 
 [ 
 field_type_name 
 ] 
 ) 
 print 
 ( 
 f 
 "A default image URL 
 { 
 default_url 
 } 
 is used to create an " 
 f 
 "image asset for the asset field type 
 { 
 field_type_name 
 } 
 " 
 ) 
 operations 
 . 
 extend 
 ( 
 create_image_asset_and_image_asset_group_asset_operations 
 ( 
 client 
 , 
 customer_id 
 , 
 default_url 
 , 
 field_type_enum 
 , 
 name 
 ) 
 ) 
 return 
 operations 
 def 
  
 create_image_asset_and_image_asset_group_asset_operations 
 ( 
 client 
 : 
 GoogleAdsClient 
 , 
 customer_id 
 : 
 str 
 , 
 url 
 : 
 str 
 , 
 field_type_enum 
 : 
 AssetFieldTypeEnum 
 . 
 AssetFieldType 
 , 
 asset_name 
 : 
 str 
 , 
 ) 
 - 
> List 
 [ 
 MutateOperation 
 ]: 
  
 """Creates a list of mutate operations that create a new linked image asset. 
 Args: 
 client: an initialized GoogleAdsClient instance. 
 customer_id: a client customer ID. 
 url: the URL of the image to be retrieved and put into an asset. 
 field_type_enum: the field type enum of the new asset in the asset group 
 asset. 
 asset_name: the asset name. 
 Returns: 
 a list of mutate operations that create a new linked image asset. 
 """ 
 global 
 next_temp_id 
 googleads_service 
 : 
 GoogleAdsServiceClient 
 = 
 client 
 . 
 get_service 
 ( 
 "GoogleAdsService" 
 ) 
 operations 
 : 
 List 
 [ 
 MutateOperation 
 ] 
 = 
 [] 
 # Creates a new mutate operation that creates an image asset. 
 asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset 
 : 
 Asset 
 = 
 asset_mutate_operation 
 . 
 asset_operation 
 . 
 create 
 asset 
 . 
 resource_name 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 next_temp_id 
 ) 
 # Provide a unique friendly name to identify your asset. When there is an 
 # existing image asset with the same content but a different name, the new 
 # name will be dropped silently. 
 asset 
 . 
 name 
 = 
 asset_name 
 asset 
 . 
 image_asset 
 . 
 data 
 = 
 get_image_bytes_from_url 
 ( 
 url 
 ) 
 operations 
 . 
 append 
 ( 
 asset_mutate_operation 
 ) 
 # Creates an asset group asset operation to link the asset to the asset 
 # group. 
 asset_group_asset_mutate_operation 
 : 
 MutateOperation 
 = 
 client 
 . 
 get_type 
 ( 
 "MutateOperation" 
 ) 
 asset_group_asset 
 : 
 AssetGroupAsset 
 = 
 ( 
 asset_group_asset_mutate_operation 
 . 
 asset_group_asset_operation 
 . 
 create 
 ) 
 asset_group_asset 
 . 
 asset 
 = 
 googleads_service 
 . 
 asset_path 
 ( 
 customer_id 
 , 
 next_temp_id 
 ) 
 asset_group_asset 
 . 
 asset_group 
 = 
 googleads_service 
 . 
 asset_group_path 
 ( 
 customer_id 
 , 
 ASSET_GROUP_TEMPORARY_ID 
 ) 
 asset_group_asset 
 . 
 field_type 
 = 
 field_type_enum 
 operations 
 . 
 append 
 ( 
 asset_group_asset_mutate_operation 
 ) 
 next_temp_id 
 -= 
 1 
 return 
 operations 
 def 
  
 print_response_details 
 ( 
 mutate_response 
 : 
 MutateGoogleAdsResponse 
 ) 
 - 
> None 
 : 
  
 """Prints the details of a MutateGoogleAdsResponse message. 
 Parses the "response" oneof field name and uses it to extract the new 
 entity's name and resource name. 
 Args: 
 mutate_response: a MutateGoogleAdsResponse message. 
 """ 
 result 
 : 
 MutateOperationResponse 
 for 
 result 
 in 
 mutate_response 
 . 
 mutate_operation_responses 
 : 
 resource_type 
 : 
 str 
 = 
 "unrecognized" 
 resource_name 
 : 
 str 
 = 
 "not found" 
 if 
 "asset_result" 
 in 
 result 
 : 
 resource_type 
 = 
 "Asset" 
 resource_name 
 = 
 result 
 . 
 asset_result 
 . 
 resource_name 
 elif 
 "asset_set_asset_result" 
 in 
 result 
 : 
 resource_type 
 = 
 "AssetSetAsset" 
 resource_name 
 = 
 result 
 . 
 asset_set_asset_result 
 . 
 resource_name 
 elif 
 "campaign_budget_result" 
 in 
 result 
 : 
 resource_type 
 = 
 "CampaignBudget" 
 resource_name 
 = 
 result 
 . 
 campaign_budget_result 
 . 
 resource_name 
 elif 
 "campaign_result" 
 in 
 result 
 : 
 resource_type 
 = 
 "Campaign" 
 resource_name 
 = 
 result 
 . 
 campaign_result 
 . 
 resource_name 
 elif 
 "asset_group_result" 
 in 
 result 
 : 
 resource_type 
 = 
 "AssetGroup" 
 resource_name 
 = 
 result 
 . 
 asset_group_result 
 . 
 resource_name 
 elif 
 "asset_group_asset_result" 
 in 
 result 
 : 
 resource_type 
 = 
 "AssetGroupAsset" 
 resource_name 
 = 
 result 
 . 
 asset_group_asset_result 
 . 
 resource_name 
 print 
 ( 
 f 
 "Created a(n) 
 { 
 resource_type 
 } 
 with " 
 f 
 "resource_name: ' 
 { 
 resource_name 
 } 
 '." 
 ) 
 if 
 __name__ 
 == 
 "__main__" 
 : 
 parser 
 : 
 argparse 
 . 
 ArgumentParser 
 = 
 argparse 
 . 
 ArgumentParser 
 ( 
 description 
 = 
 ( 
 "Creates a Performance Max for travel goals 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 
 ( 
 "-p" 
 , 
 "--place_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 ( 
 "Sets a place ID that uniquely identifies a place in the Google " 
 "Places database. The provided place ID must belong to a hotel " 
 "property. To learn more, see: " 
 "https://developers.google.com/places/web-service/place-id " 
 ), 
 ) 
 args 
 : 
 argparse 
 . 
 Namespace 
 = 
 parser 
 . 
 parse_args 
 () 
 # GoogleAdsClient will read the google-ads.yaml configuration 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 
 . 
 place_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 
 'Error with message " 
 { 
 error 
 . 
 message 
 } 
 ".' 
 ) 
 if 
 error 
 . 
 location 
 : 
 for 
 field_path_element 
 in 
 error 
 . 
 location 
 . 
 field_path_elements 
 : 
 print 
 ( 
 f 
 " 
 \t\t 
 On field: 
 { 
 field_path_element 
 . 
 field_name 
 } 
 " 
 ) 
 sys 
 . 
 exit 
 ( 
 1 
 ) 
  

Ruby

 #!/usr/bin/env ruby 
 # Encoding: utf-8 
 # 
 # Copyright 2023 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 create a Performance Max for travel goals campaign. 
 # It also uses TravelAssetSuggestionService to fetch suggested assets for 
 # creating an asset group. In case there are not enough assets for the asset 
 # group (required by Performance Max), this example will create more assets to 
 # fulfill the requirements. 
 # 
 # For more information about Performance Max campaigns, see 
 # https://developers.google.com/google-ads/api/docs/performance-max/overview. 
 # 
 # Prerequisites: 
 # - You must have at least one conversion action in the account. 
 # For more about conversion actions, see 
 # https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
 # 
 # Notes: 
 # - This example uses the default customer conversion goals. 
 #   For an example of setting campaign-specific conversion goals, see 
 #   shopping_ads/add_performance_max_retail_campaign.rb 
 # - To learn how to create asset group signals, see 
 #   advanced_perations/add_performance_max_campaign.rb 
 require 
  
 'open-uri' 
 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. 
 # 
 # These temporary IDs are fixed because they are used in multiple places. 
 BUDGET_TEMPORARY_ID 
  
 = 
  
 '-1' 
 PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID 
  
 = 
  
 '-2' 
 ASSET_GROUP_TEMPORARY_ID 
  
 = 
  
 '-3' 
 # There are also entities that will be created in the same request but do not 
 # need to be fixed temporary IDs because they are referenced only once. 
 def 
  
 next_temp_id 
  
 @id 
  
 ||= 
  
 ASSET_GROUP_TEMPORARY_ID 
 . 
 to_i 
  
 @id 
  
 -= 
  
 1 
 end 
 # Minimum requirements of assets required in a Performance Max Asset Group. 
 # See https://developers.google.com/google-ads/api/docs/performance-max/assets 
 # for details. 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
  
 = 
  
 { 
  
 HEADLINE 
 : 
  
 3 
 , 
  
 LONG_HEADLINE 
 : 
  
 1 
 , 
  
 DESCRIPTION 
 : 
  
 2 
 , 
  
 BUSINESS_NAME 
 : 
  
 1 
 } 
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
  
 = 
  
 { 
  
 MARKETING_IMAGE 
 : 
  
 1 
 , 
  
 SQUARE_MARKETING_IMAGE 
 : 
  
 1 
 , 
  
 LOGO 
 : 
  
 1 
 } 
 # Texts and URLs used to create text and image assets when the 
 # TravelAssetSuggestionService doesn't return enough assets required for 
 # creating an asset group. 
 DEFAULT_TEXT_ASSETS_INFO 
  
 = 
  
 { 
  
 HEADLINE 
 : 
  
 [ 
 'Hotel' 
 , 
  
 'Travel Reviews' 
 , 
  
 'Book travel' 
 ] 
 , 
  
 LONG_HEADLINE 
 : 
  
 [ 
 'Travel the World' 
 ] 
 , 
  
 DESCRIPTION 
 : 
  
 [ 
 'Great deal for your beloved hotel' 
 , 
  
 'Best rate guaranteed' 
 ] 
 , 
  
 BUSINESS_NAME 
 : 
  
 [ 
 'Interplanetary Cruises' 
 ] 
 } 
 DEFAULT_IMAGE_ASSETS_INFO 
  
 = 
  
 { 
  
 MARKETING_IMAGE 
 : 
  
 [ 
 'https://gaagl.page.link/Eit5' 
 ] 
 , 
  
 SQUARE_MARKETING_IMAGE 
 : 
  
 [ 
 'https://gaagl.page.link/bjYi' 
 ] 
 , 
  
 LOGO 
 : 
  
 [ 
 'https://gaagl.page.link/bjYi' 
 ] 
 } 
 def 
  
 add_performance_max_for_travel_goals 
 ( 
 customer_id 
 , 
  
 place_id 
 ) 
  
 # GoogleAdsClient will read a config file from 
  
 # ENV['HOME']/google_ads_config.rb when called without parameters 
  
 client 
  
 = 
  
 Google 
 :: 
 Ads 
 :: 
 GoogleAds 
 :: 
 GoogleAdsClient 
 . 
 new 
  
 # Creates a hotel property asset set, which will be used later to link with a 
  
 # newly created campaign. 
  
 hotel_property_asset_set_resource_name 
  
 = 
  
 create_hotel_asset_set 
 ( 
 client 
 , 
  
 customer_id 
 ) 
  
 # Creates a hotel property asset and link it with the previously created hotel property 
  
 # asset set. This asset will also be linked to an asset group in the later steps. 
  
 # In the real-world scenario, you'd need to create many assets for all your hotel 
  
 # properties. We use one hotel property here for simplicity. 
  
 # Both asset and asset set need to be created before creating a campaign, 
  
 # so we cannot bundle them with other mutate operations below. 
  
 hotel_property_asset_resource_name 
  
 = 
  
 create_hotel_asset 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 place_id 
 , 
  
 hotel_property_asset_set_resource_name 
  
 ) 
  
 # The below methods create and return MutateOperations that we later 
  
 # provide to the GoogleAdsService.Mutate method in order to create the 
  
 # entities in a single request. Since the entities for a Performance Max 
  
 # campaign are closely tied to one-another, it's considered a best practice 
  
 # to create them in a single Mutate request so they all complete 
  
 # successfully or fail entirely, leaving no orphaned entities. See: 
  
 # https://developers.google.com/google-ads/api/docs/mutating/overview 
  
 campaign_budget_operation 
  
 = 
  
 create_campaign_budget_operation 
 ( 
 client 
 , 
  
 customer_id 
 ) 
  
 performance_max_campaign_operation 
  
 = 
  
 create_performance_max_campaign_operation 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_property_asset_set_resource_name 
  
 ) 
  
 # Gets hotel asset suggestion using the TravelAssetSuggestionService. 
  
 hotel_asset_suggestion 
  
 = 
  
 get_hotel_asset_suggestion 
 ( 
 client 
 , 
  
 customer_id 
 , 
  
 place_id 
 ) 
  
 # Creates the headlines using the hotel asset suggestion. 
  
 headline_asset_resource_names 
  
 = 
  
 create_multiple_text_assets 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 :HEADLINE 
 , 
  
 hotel_asset_suggestion 
  
 ) 
  
 # Creates the descriptions using the hotel asset suggestion. 
  
 descriptions_asset_resource_names 
  
 = 
  
 create_multiple_text_assets 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 :DESCRIPTION 
 , 
  
 hotel_asset_suggestion 
  
 ) 
  
 asset_group_operation 
  
 = 
  
 create_asset_group_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_property_asset_resource_name 
 , 
  
 headline_asset_resource_names 
 , 
  
 descriptions_asset_resource_names 
 , 
  
 hotel_asset_suggestion 
  
 ) 
  
 # Send the operations in a single Mutate request. 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 google_ads 
 . 
 mutate 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 mutate_operations 
 : 
  
 [ 
  
 # It's important to create these entities in this order because 
  
 # they depend on each other. 
  
 campaign_budget_operation 
 , 
  
 performance_max_campaign_operation 
 , 
  
 asset_group_operation 
  
 ]. 
 flatten 
  
 ) 
  
 print_response_details 
 ( 
 response 
 ) 
 end 
 # Creates a hotel property asset set. 
 def 
  
 create_hotel_asset_set 
 ( 
 client 
 , 
  
 customer_id 
 ) 
  
 operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_set 
  
 do 
  
 | 
 asset_set 
 | 
  
 asset_set 
 . 
 name 
  
 = 
  
 "My Hotel propery asset set 
 #{ 
 Time 
 . 
 now 
 } 
 " 
  
 asset_set 
 . 
 type 
  
 = 
  
 :HOTEL_PROPERTY 
  
 end 
  
 # Sends the mutate request. 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 asset_set 
 . 
 mutate_asset_sets 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 operations 
 : 
  
 [ 
 operation 
 ] 
  
 ) 
  
 # Prints some information about the response. 
  
 response 
 . 
 results 
 . 
 first 
 . 
 resource_name 
 end 
 # Creates a hotel property asset using the specified place ID. 
 # The place ID must belong to a hotel property. Then, links it to the 
 # specified asset set. 
 # See https://developers.google.com/places/web-service/place-id to search for a 
 # hotel place ID. 
 def 
  
 create_hotel_asset 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 place_id 
 , 
  
 hotel_property_asset_set_resource_name 
 ) 
  
 asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset 
  
 do 
  
 | 
 asset 
 | 
  
 asset 
 . 
 name 
  
 = 
  
 'Ad Media Bundle' 
  
 asset 
 . 
 hotel_property_asset 
  
 = 
  
 client 
 . 
 resource 
 . 
 hotel_property_asset 
  
 do 
  
 | 
 hotel_asset 
 | 
  
 hotel_asset 
 . 
 place_id 
  
 = 
  
 place_id 
  
 end 
  
 end 
  
 # Send the mutate request. 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 asset 
 . 
 mutate_assets 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 operations 
 : 
  
 [ 
 asset_operation 
 ] 
  
 ) 
  
 asset_resource_name 
  
 = 
  
 response 
 . 
 results 
 . 
 first 
 . 
 resource_name 
  
 # Creates a mutate operation for an asset set asset. 
  
 asset_set_asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_set_asset 
  
 do 
  
 | 
 asa 
 | 
  
 asa 
 . 
 asset 
  
 = 
  
 asset_resource_name 
  
 asa 
 . 
 asset_set 
  
 = 
  
 hotel_property_asset_set_resource_name 
  
 end 
  
 # Sends the mutate request. 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 asset_set_asset 
 . 
 mutate_asset_set_assets 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 operations 
 : 
  
 [ 
 asset_set_asset_operation 
 ] 
  
 ) 
  
 asset_resource_name 
 end 
 # Creates a MutateOperation that creates a new Performance Max campaign. 
 # 
 # A temporary ID will be assigned to this campaign so that it can 
 # be referenced by other objects being created in the same Mutate request. 
 def 
  
 create_performance_max_campaign_operation 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_property_asset_set_resource_name 
 ) 
  
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 campaign_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 campaign 
  
 do 
  
 | 
 c 
 | 
  
 c 
 . 
 name 
  
 = 
  
 "Performance Max for Travel Goals 
 #{ 
 SecureRandom 
 . 
 uuid 
 } 
 " 
  
 # Set the campaign status as PAUSED. The campaign is the only entity in 
  
 # the mutate request that should have its status set. 
  
 c 
 . 
 status 
  
 = 
  
 :PAUSED 
  
 # All Performance Max campaigns have an advertising_channel_type of 
  
 # PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. 
  
 c 
 . 
 advertising_channel_type 
  
 = 
  
 :PERFORMANCE_MAX 
  
 # Declare whether or not this campaign serves political ads targeting the EU. 
  
 # Valid values are CONTAINS_EU_POLITICAL_ADVERTISING and 
  
 # DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING. 
  
 c 
 . 
 contains_eu_political_advertising 
  
 = 
  
 :DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING 
  
 # To create a Performance Max for travel goals campaign, you need to set hotel_property_asset_set 
  
 c 
 . 
 hotel_property_asset_set 
  
 = 
  
 hotel_property_asset_set_resource_name 
  
 # Bidding strategy must be set directly on the campaign. 
  
 # Setting a portfolio bidding strategy by resource name is not supported. 
  
 # Max Conversion and Maximize Conversion Value are the only strategies 
  
 # supported for Performance Max campaigns. 
  
 # An optional ROAS (Return on Advertising Spend) can be set for 
  
 # maximize_conversion_value. The ROAS value must be specified as a ratio 
  
 # in the API. It is calculated by dividing "total value" by "total spend". 
  
 # For more information on Maximize Conversion Value, see the support 
  
 # article: http://support.google.com/google-ads/answer/7684216. 
  
 # A target_roas of 3.5 corresponds to a 350% return on ad spend. 
  
 c 
 . 
 bidding_strategy_type 
  
 = 
  
 :MAXIMIZE_CONVERSION_VALUE 
  
 c 
 . 
 maximize_conversion_value 
  
 = 
  
 client 
 . 
 resource 
 . 
 maximize_conversion_value 
  
 do 
  
 | 
 mcv 
 | 
  
 mcv 
 . 
 target_roas 
  
 = 
  
 3 
 . 
 5 
  
 end 
  
 # Assign the resource name with a temporary ID. 
  
 c 
 . 
 resource_name 
  
 = 
  
 client 
 . 
 path 
 . 
 campaign 
 ( 
  
 customer_id 
 , 
  
 PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID 
  
 ) 
  
 # Set the budget using the given budget resource name. 
  
 c 
 . 
 campaign_budget 
  
 = 
  
 client 
 . 
 path 
 . 
 campaign_budget 
 ( 
 customer_id 
 , 
  
 BUDGET_TEMPORARY_ID 
 ) 
  
 end 
  
 end 
 end 
 def 
  
 create_campaign_budget_operation 
 ( 
 client 
 , 
  
 customer_id 
 ) 
  
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 campaign_budget_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 campaign_budget 
  
 do 
  
 | 
 cb 
 | 
  
 cb 
 . 
 name 
  
 = 
  
 "Performance Max campaign budget 
 #{ 
 SecureRandom 
 . 
 uuid 
 } 
 " 
  
 # The budget period already defaults to DAILY. 
  
 cb 
 . 
 amount_micros 
  
 = 
  
 50_000_000 
  
 cb 
 . 
 delivery_method 
  
 = 
  
 :STANDARD 
  
 # A Performance Max campaign cannot use a shared campaign budget. 
  
 cb 
 . 
 explicitly_shared 
  
 = 
  
 false 
  
 # Set a temporary ID in the budget's resource name so it can be referenced 
  
 # by the campaign in later steps. 
  
 cb 
 . 
 resource_name 
  
 = 
  
 client 
 . 
 path 
 . 
 campaign_budget 
 ( 
 customer_id 
 , 
  
 BUDGET_TEMPORARY_ID 
 ) 
  
 end 
  
 end 
 end 
 def 
  
 get_hotel_asset_suggestion 
 ( 
 client 
 , 
  
 customer_id 
 , 
  
 place_id 
 ) 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 travel_asset_suggestion 
 . 
 suggest_travel_assets 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 language_option 
 : 
  
 'en-US' 
 , 
  
 place_ids 
 : 
  
 [ 
 place_id 
 ] 
  
 ) 
  
 response 
 . 
 hotel_asset_suggestions 
 . 
 first 
 end 
 # Creates multiple text assets and returns the list of resource names. 
 # The hotel asset suggestion is used to create a text asset first. 
 # If the number of created text assets is still fewer than the minimum required 
 # number of assets of the specified asset field type, adds more text assets to 
 # fulfill the requirement. 
 def 
  
 create_multiple_text_assets 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 asset_field_type 
 , 
  
 hotel_asset_suggestion 
 ) 
  
 # Creates the first text asset using the hotel asset suggestions. 
  
 texts 
  
 = 
  
 [] 
  
 numText 
  
 = 
  
 0 
  
 if 
  
 hotel_asset_suggestion 
 . 
 status 
  
 == 
  
 :SUCCESS 
  
 hotel_asset_suggestion 
 . 
 text_assets 
 . 
 each 
  
 do 
  
 | 
 hotel_text_asset 
 | 
  
 if 
  
 hotel_text_asset 
 . 
 asset_field_type 
  
 == 
  
 asset_field_type 
  
 texts 
 . 
 append 
 ( 
 hotel_text_asset 
 . 
 text 
 ) 
  
 numText 
  
 += 
  
 1 
  
 end 
  
 end 
  
 end 
  
 # If the added assets are still less than the minimum required assets for the 
  
 # asset field type, add more text assets using the default texts. 
  
 if 
  
 numText 
 < 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 [ 
 asset_field_type 
 ] 
  
 for 
  
 i 
  
 in 
  
 numText 
 .. 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 [ 
 asset_field_type 
 ] 
  
 - 
  
 1 
  
 texts 
 . 
 append 
 ( 
 DEFAULT_TEXT_ASSETS_INFO 
 [ 
 asset_field_type 
 ][ 
 i 
 ] 
 ) 
  
 end 
  
 end 
  
 # Create the operations with the selected texts. 
  
 operations 
  
 = 
  
 texts 
 . 
 map 
  
 do 
  
 | 
 text 
 | 
  
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset 
  
 do 
  
 | 
 asset 
 | 
  
 asset 
 . 
 text_asset 
  
 = 
  
 client 
 . 
 resource 
 . 
 text_asset 
  
 { 
  
 | 
 text_asset 
 | 
  
 text_asset 
 . 
 text 
  
 = 
  
 text 
  
 } 
  
 end 
  
 end 
  
 end 
  
 # Send the operations in a single Mutate request. 
  
 response 
  
 = 
  
 client 
 . 
 service 
 . 
 google_ads 
 . 
 mutate 
 ( 
  
 customer_id 
 : 
  
 customer_id 
 , 
  
 mutate_operations 
 : 
  
 operations 
  
 ) 
  
 asset_resource_names 
  
 = 
  
 [] 
  
 response 
 . 
 mutate_operation_responses 
 . 
 each 
  
 do 
  
 | 
 result 
 | 
  
 asset_resource_names 
 . 
 append 
 ( 
 result 
 . 
 asset_result 
 . 
 resource_name 
 ) 
  
 if 
  
 result 
 . 
 asset_result 
  
 end 
  
 print_response_details 
 ( 
 response 
 ) 
  
 asset_resource_names 
 end 
 # Creates a list of MutateOperations that create a new asset_group. 
 # 
 # A temporary ID will be assigned to this asset group so that it can 
 # be referenced by other objects being created in the same Mutate request. 
 def 
  
 create_asset_group_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_property_asset_resource_name 
 , 
  
 headline_asset_resource_names 
 , 
  
 description_asset_resource_names 
 , 
  
 hotel_asset_suggestion 
 ) 
  
 operations 
  
 = 
  
 [] 
  
 # Creates a new mutate operation that creates an asset group using suggested 
  
 # information when available. 
  
 asset_group_final_urls 
  
 = 
  
 ( 
  
 if 
  
 hotel_asset_suggestion 
 . 
 status 
  
 == 
  
 :SUCCESS 
  
 hotel_asset_suggestion 
 . 
 final_url 
  
 else 
  
 'http://www.example.com' 
  
 end 
  
 ) 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_group_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_group 
  
 do 
  
 | 
 ag 
 | 
  
 ag 
 . 
 name 
  
 = 
  
 "Performance Max for Travel Goals asset group 
 #{ 
 SecureRandom 
 . 
 uuid 
 } 
 " 
  
 ag 
 . 
 campaign 
  
 = 
  
 client 
 . 
 path 
 . 
 campaign 
 ( 
  
 customer_id 
 , 
  
 PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID 
  
 ) 
  
 ag 
 . 
 final_urls 
 << 
 asset_group_final_urls 
  
 ag 
 . 
 status 
  
 = 
  
 :PAUSED 
  
 ag 
 . 
 resource_name 
  
 = 
  
 client 
 . 
 path 
 . 
 asset_group 
 ( 
 customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ) 
  
 end 
  
 end 
  
 # An AssetGroup is linked to an Asset by creating a new AssetGroupAsset 
  
 # and providing: 
  
 #   the resource name of the AssetGroup 
  
 #   the resource name of the Asset 
  
 #   the field_type of the Asset in this AssetGroup. 
  
 # 
  
 # To learn more about AssetGroups, see 
  
 # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups 
  
 # Link the previously created multiple text assets. 
  
 # Link the headline assets. 
  
 headline_asset_resource_names 
 . 
 each 
  
 do 
  
 | 
 resource_name 
 | 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_group_asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_group_asset 
  
 do 
  
 | 
 aga 
 | 
  
 aga 
 . 
 field_type 
  
 = 
  
 :HEADLINE 
  
 aga 
 . 
 asset_group 
  
 = 
  
 client 
 . 
 path 
 . 
 asset_group 
 ( 
 customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ) 
  
 aga 
 . 
 asset 
  
 = 
  
 resource_name 
  
 end 
  
 end 
  
 end 
  
 #  Link the description assets. 
  
 description_asset_resource_names 
 . 
 each 
  
 do 
  
 | 
 resource_name 
 | 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_group_asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_group_asset 
  
 do 
  
 | 
 aga 
 | 
  
 aga 
 . 
 field_type 
  
 = 
  
 :DESCRIPTION 
  
 aga 
 . 
 asset_group 
  
 = 
  
 client 
 . 
 path 
 . 
 asset_group 
 ( 
 customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ) 
  
 aga 
 . 
 asset 
  
 = 
  
 resource_name 
  
 end 
  
 end 
  
 end 
  
 # Link the previously created hotel property asset to the asset group. 
  
 # In the real-world scenario, you'd need to do this step several times for 
  
 # each hotel property asset. 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_group_asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_group_asset 
  
 do 
  
 | 
 aga 
 | 
  
 aga 
 . 
 field_type 
  
 = 
  
 :HOTEL_PROPERTY 
  
 aga 
 . 
 asset_group 
  
 = 
  
 client 
 . 
 path 
 . 
 asset_group 
 ( 
 customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ) 
  
 aga 
 . 
 asset 
  
 = 
  
 hotel_property_asset_resource_name 
  
 end 
  
 end 
  
 # Creates the rest of required text assets and link them to the asset group. 
  
 operations 
  
 += 
  
 create_text_assets_for_asset_group 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_asset_suggestion 
  
 ) 
  
 # Creates the image assets and link them to the asset group. Some optional 
  
 # image assets suggested by the TravelAssetSuggestionService might be created too. 
  
 operations 
  
 += 
  
 create_image_assets_for_asset_group 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_asset_suggestion 
  
 ) 
  
 operations 
 end 
 # Creates text assets required for an asset group using the suggested hotel text 
 # assets. It adds more text assets to fulfill the requirements if the suggested 
 # hotel text assets are not enough. 
 def 
  
 create_text_assets_for_asset_group 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_asset_suggestion 
 ) 
  
 operations 
  
 = 
  
 [] 
  
 required_text_asset_counts 
  
 = 
  
 deep_copy 
 ( 
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 ) 
  
 required_text_asset_counts 
 . 
 each 
  
 do 
  
 | 
 asset_field_type 
 , 
  
 _count 
 | 
  
 required_text_asset_counts 
 [ 
 asset_field_type 
 ] 
  
 = 
  
 0 
  
 end 
  
 # Creates mutate operations for the suggested text assets except for headlines 
  
 # and descriptions, which were created previously. 
  
 if 
  
 hotel_asset_suggestion 
 . 
 status 
  
 == 
  
 :SUCCESS 
  
 hotel_asset_suggestion 
 . 
 text_assets 
 . 
 each 
  
 do 
  
 | 
 hotel_text_asset 
 | 
  
 if 
  
 hotel_text_asset 
 . 
 asset_field_type 
  
 == 
  
 :HEADLINE 
  
 || 
  
 hotel_text_asset 
 . 
 asset_field_type 
  
 == 
  
 :DESCRIPTION 
  
 # Headlines and descriptions were already created at the first step of 
  
 # this code example. 
  
 next 
  
 end 
  
 puts 
  
 "A text asset with text ' 
 #{ 
 hotel_text_asset 
 . 
 text 
 } 
 ' is suggested for the asset field type ' 
 #{ 
 hotel_text_asset 
 . 
 asset_field_type 
 } 
 '." 
  
 operations 
  
 += 
  
 create_and_link_text_asset_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_text_asset 
 . 
 text 
 , 
  
 hotel_text_asset 
 . 
 asset_field_type 
  
 ) 
  
 required_text_asset_counts 
 [ 
 hotel_text_asset 
 . 
 asset_field_type 
 ] 
  
 += 
  
 1 
  
 end 
  
 end 
  
 # Adds more text assets to fulfill the requirements. 
  
 MIN_REQUIRED_TEXT_ASSET_COUNTS 
 . 
 each 
  
 do 
  
 | 
 asset_field_type 
 , 
  
 count 
 | 
  
 if 
  
 % 
 i 
 [ 
 HEADLINE 
  
 DESCRIPTION 
 ]. 
 include? 
 ( 
 asset_field_type 
 ) 
  
 # Headlines and descriptions were already created at the first step of this 
  
 # code example. 
  
 next 
  
 end 
  
 next 
  
 unless 
  
 required_text_asset_counts 
 [ 
 asset_field_type 
 ] 
 < 
 count 
  
 for 
  
 i 
  
 in 
  
 required_text_asset_counts 
 [ 
 asset_field_type 
 ]... 
 count 
  
 default_text 
  
 = 
  
 DEFAULT_TEXT_ASSETS_INFO 
 [ 
 asset_field_type 
 ][ 
 i 
 ] 
  
 puts 
  
 "A default text ' 
 #{ 
 default_text 
 } 
 ' is used to create a text asset for the asset field type ' 
 #{ 
 asset_field_type 
 } 
 '." 
  
 operations 
  
 += 
  
 create_and_link_text_asset_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 default_text 
 , 
  
 asset_field_type 
  
 ) 
  
 end 
  
 end 
  
 operations 
 end 
 # Creates a list of MutateOperations that create a new linked text asset. 
 def 
  
 create_and_link_text_asset_operations 
 ( 
 client 
 , 
  
 customer_id 
 , 
  
 text 
 , 
  
 field_type 
 ) 
  
 operations 
  
 = 
  
 [] 
  
 temp_id 
  
 = 
  
 next_temp_id 
  
 # Create the Text Asset. 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset 
  
 do 
  
 | 
 a 
 | 
  
 a 
 . 
 resource_name 
  
 = 
  
 client 
 . 
 path 
 . 
 asset 
 ( 
 customer_id 
 , 
  
 temp_id 
 ) 
  
 a 
 . 
 text_asset 
  
 = 
  
 client 
 . 
 resource 
 . 
 text_asset 
  
 { 
  
 | 
 text_asset 
 | 
  
 text_asset 
 . 
 text 
  
 = 
  
 text 
  
 } 
  
 end 
  
 end 
  
 # Create an AssetGroupAsset to link the Asset to the AssetGroup. 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_group_asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_group_asset 
  
 do 
  
 | 
 aga 
 | 
  
 aga 
 . 
 field_type 
  
 = 
  
 field_type 
  
 aga 
 . 
 asset_group 
  
 = 
  
 client 
 . 
 path 
 . 
 asset_group 
 ( 
 customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ) 
  
 aga 
 . 
 asset 
  
 = 
  
 client 
 . 
 path 
 . 
 asset 
 ( 
 customer_id 
 , 
  
 temp_id 
 ) 
  
 end 
  
 end 
  
 operations 
 end 
 # Creates image assets required for an asset group using the suggested hotel 
 # image assets. It adds more image assets to fulfill the requirements if the 
 # suggested hotel image assets are not enough. 
 def 
  
 create_image_assets_for_asset_group 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_asset_suggestion 
 ) 
  
 operations 
  
 = 
  
 [] 
  
 required_image_asset_counts 
  
 = 
  
 deep_copy 
 ( 
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 ) 
  
 required_image_asset_counts 
 . 
 each 
  
 do 
  
 | 
 asset_field_type 
 , 
  
 _count 
 | 
  
 required_image_asset_counts 
 [ 
 asset_field_type 
 ] 
  
 = 
  
 0 
  
 end 
  
 # Creates mutate operations for the suggested image assets. 
  
 if 
  
 hotel_asset_suggestion 
 . 
 status 
  
 == 
  
 :SUCCESS 
  
 hotel_asset_suggestion 
 . 
 image_assets 
 . 
 each 
  
 do 
  
 | 
 hotel_image_asset 
 | 
  
 puts 
  
 "An image asset with URL ' 
 #{ 
 hotel_image_asset 
 . 
 uri 
 } 
 ' is suggested for the asset field type ' 
 #{ 
 hotel_image_asset 
 . 
 asset_field_type 
 } 
 '." 
  
 operations 
  
 += 
  
 create_and_link_image_asset_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 hotel_image_asset 
 . 
 uri 
 , 
  
 hotel_image_asset 
 . 
 asset_field_type 
 , 
  
 "Suggested image asset for the asset field type '%s'." 
  
 % 
  
 hotel_image_asset 
 . 
 asset_field_type 
  
 ) 
  
 # Keeps track of only required image assets. The service may sometimes 
  
 # suggest optional image assets. 
  
 unless 
  
 required_image_asset_counts 
 . 
 has_key? 
 ( 
  
 hotel_image_asset 
 . 
 asset_field_type 
  
 ) 
  
 next 
  
 end 
  
 required_image_asset_counts 
 [ 
 hotel_image_asset 
 . 
 asset_field_type 
 ] 
  
 += 
  
 1 
  
 end 
  
 end 
  
 # Adds more image assets to fulfill the requirements. 
  
 MIN_REQUIRED_IMAGE_ASSET_COUNTS 
 . 
 each 
  
 do 
  
 | 
 asset_field_type 
 , 
  
 count 
 | 
  
 next 
  
 unless 
  
 required_image_asset_counts 
 [ 
 asset_field_type 
 ] 
 < 
 count 
  
 for 
  
 i 
  
 in 
  
 required_image_asset_counts 
 [ 
 asset_field_type 
 ]... 
 count 
  
 default_uri 
  
 = 
  
 DEFAULT_IMAGE_ASSETS_INFO 
 [ 
 asset_field_type 
 ][ 
 i 
 ] 
  
 puts 
  
 "A default image URL ' 
 #{ 
 default_uri 
 } 
 ' is used to create an image asset for the asset field type ' 
 #{ 
 asset_field_type 
 } 
 '." 
  
 operations 
  
 += 
  
 create_and_link_image_asset_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 default_uri 
 , 
  
 asset_field_type 
 , 
  
 "Default image asset for the asset field type '%s'." 
  
 % 
  
 asset_field_type 
  
 ) 
  
 end 
  
 end 
  
 operations 
 end 
 # Creates a list of MutateOperations that create a new linked image asset. 
 def 
  
 create_and_link_image_asset_operations 
 ( 
  
 client 
 , 
  
 customer_id 
 , 
  
 url 
 , 
  
 field_type 
 , 
  
 asset_name 
 ) 
  
 operations 
  
 = 
  
 [] 
  
 temp_id 
  
 = 
  
 next_temp_id 
  
 # Create the Image Asset. 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset 
  
 do 
  
 | 
 a 
 | 
  
 a 
 . 
 resource_name 
  
 = 
  
 client 
 . 
 path 
 . 
 asset 
 ( 
 customer_id 
 , 
  
 temp_id 
 ) 
  
 # Provide a unique friendly name to identify your asset. 
  
 # When there is an existing image asset with the same content but a different 
  
 # name, the new name will be dropped silently. 
  
 a 
 . 
 name 
  
 = 
  
 asset_name 
  
 a 
 . 
 type 
  
 = 
  
 :IMAGE 
  
 a 
 . 
 image_asset 
  
 = 
  
 client 
 . 
 resource 
 . 
 image_asset 
  
 do 
  
 | 
 image_asset 
 | 
  
 image_asset 
 . 
 data 
  
 = 
  
 get_image_bytes 
 ( 
 url 
 ) 
  
 end 
  
 end 
  
 end 
  
 # Create an AssetGroupAsset to link the Asset to the AssetGroup. 
  
 operations 
 << 
 client 
 . 
 operation 
 . 
 mutate 
  
 do 
  
 | 
 m 
 | 
  
 m 
 . 
 asset_group_asset_operation 
  
 = 
  
 client 
 . 
 operation 
 . 
 create_resource 
 . 
 asset_group_asset 
  
 do 
  
 | 
 aga 
 | 
  
 aga 
 . 
 field_type 
  
 = 
  
 field_type 
  
 aga 
 . 
 asset_group 
  
 = 
  
 client 
 . 
 path 
 . 
 asset_group 
 ( 
 customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ) 
  
 aga 
 . 
 asset 
  
 = 
  
 client 
 . 
 path 
 . 
 asset 
 ( 
 customer_id 
 , 
  
 temp_id 
 ) 
  
 end 
  
 end 
  
 operations 
 end 
 # Loads image data from a URL. 
 def 
  
 get_image_bytes 
 ( 
 url 
 ) 
  
 URI 
 . 
 open 
 ( 
 url 
 ) 
 . 
 read 
 end 
 # Prints the details of a MutateGoogleAdsResponse. 
 def 
  
 print_response_details 
 ( 
 response 
 ) 
  
 # Parse the mutate response to print details about the entities that 
  
 # were created by the request. 
  
 suffix 
  
 = 
  
 '_result' 
  
 response 
 . 
 mutate_operation_responses 
 . 
 each 
  
 do 
  
 | 
 result 
 | 
  
 result 
  
 . 
 to_h 
  
 . 
 select 
  
 { 
  
 | 
 _k 
 , 
  
 v 
 | 
  
 v 
  
 } 
  
 . 
 each 
  
 do 
  
 | 
 name 
 , 
  
 value 
 | 
  
 name 
  
 = 
  
 name 
 . 
 to_s 
 . 
 delete_suffix 
 ( 
 suffix 
 ) 
  
 if 
  
 name 
 . 
 to_s 
 . 
 end_with? 
 ( 
 suffix 
 ) 
  
 puts 
  
 "Created a(n) 
 #{ 
 :: 
 Google 
 :: 
 Ads 
 :: 
 GoogleAds 
 :: 
 Utils 
 . 
 camelize 
 ( 
 name 
 ) 
 } 
 " 
  
 \ 
  
 "with 
 #{ 
 value 
 . 
 to_s 
 . 
 strip 
 } 
 ." 
  
 end 
  
 end 
 end 
 def 
  
 deep_copy 
 ( 
 o 
 ) 
  
 Marshal 
 . 
 load 
 ( 
 Marshal 
 . 
 dump 
 ( 
 o 
 )) 
 end 
 if 
  
 __FILE__ 
  
 == 
  
 $0 
  
 options 
  
 = 
  
 {} 
  
 OptionParser 
  
 . 
 new 
  
 do 
  
 | 
 opts 
 | 
  
 opts 
 . 
 banner 
  
 = 
  
 format 
 ( 
 '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 
 ( 
 '-P' 
 , 
  
 '--place-id PLACE-ID' 
 , 
  
 String 
 , 
  
 'Place ID' 
 ) 
  
 do 
  
 | 
 v 
 | 
  
 options 
 [ 
 :place_id 
 ] 
  
 = 
  
 v 
  
 end 
  
 opts 
 . 
 separator 
  
 '' 
  
 opts 
 . 
 separator 
  
 'Help:' 
  
 opts 
 . 
 on_tail 
 ( 
 '-h' 
 , 
  
 '--help' 
 , 
  
 'Show this message' 
 ) 
  
 do 
  
 puts 
  
 opts 
  
 exit 
  
 end 
  
 end 
  
 . 
 parse! 
  
 begin 
  
 add_performance_max_for_travel_goals 
 ( 
  
 options 
 . 
 fetch 
 ( 
 :customer_id 
 ) 
 . 
 tr 
 ( 
 '-' 
 , 
  
 '' 
 ), 
  
 options 
 [ 
 :place_id 
 ] 
  
 ) 
  
 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 
 ( 
 " 
 \t 
 On 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 
 ( 
 " 
 \t 
 Type: %s 
 \n\t 
 Code: %s 
 \n 
 " 
 , 
  
 k 
 , 
  
 v 
 ) 
  
 end 
  
 end 
  
 raise 
  
 end 
 end 
  
  

Perl

 #!/usr/bin/perl -w 
 # 
 # Copyright 2023, 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 create a Performance Max for travel goals campaign. It also uses 
 # TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case 
 # there are not enough assets for the asset group (required by Performance Max), this example will 
 # create more assets to fulfill the requirements. 
 # 
 # For more information about Performance Max campaigns, see 
 # https://developers.google.com/google-ads/api/docs/performance-max/overview. 
 # 
 # Prerequisites: 
 # - You must have at least one conversion action in the account. For more about conversion actions, 
 # see https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
 # 
 # Notes: 
 # - This example uses the default customer conversion goals. For an example of setting 
 #   campaign-specific conversion goals, see shopping_ads/add_performance_max_retail_campaign.pl. 
 # - To learn how to create asset group signals, see 
 #   advanced_operations/add_performance_max_campaign.pl. 
 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::MediaUtils 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::CampaignBudget 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::Campaign 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::Asset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::AssetGroup 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::AssetGroupAsset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::AssetSet 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Resources::AssetSetAsset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Common::CallToActionAsset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Common::MaximizeConversionValue 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Common::TextAsset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Common::HotelPropertyAsset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Common::ImageAsset 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::BudgetDeliveryMethodEnum 
  
 qw(STANDARD) 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::CampaignStatusEnum 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::AdvertisingChannelTypeEnum 
  
 qw(PERFORMANCE_MAX) 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::AssetGroupStatusEnum 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::AssetFieldTypeEnum 
  
 qw(HEADLINE DESCRIPTION LONG_HEADLINE BUSINESS_NAME LOGO MARKETING_IMAGE SQUARE_MARKETING_IMAGE HOTEL_PROPERTY CALL_TO_ACTION_SELECTION) 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::HotelAssetSuggestionStatusEnum 
  
 qw(SUCCESS) 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Enums::EuPoliticalAdvertisingStatusEnum 
  
 qw(DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING) 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService::MutateOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::CampaignBudgetService::CampaignBudgetOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::CampaignService::CampaignOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::AssetService::AssetOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::AssetGroupService::AssetGroupOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService::AssetGroupAssetOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::AssetSetService::AssetSetOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Services::AssetSetAssetService::AssetSetAssetOperation 
 ; 
 use 
  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames 
 ; 
 use 
  
 Getopt::Long 
  
 qw(:config auto_help) 
 ; 
 use 
  
 Pod::Usage 
 ; 
 use 
  
 Cwd 
  
 qw(abs_path) 
 ; 
 use 
  
 Data::Uniqid 
  
 qw(uniqid) 
 ; 
 # Minimum requirements of assets required in a Performance Max asset group. 
 # See https://developers.google.com/google-ads/api/docs/performance-max/assets for details. 
 my 
  
 $min_required_text_asset_counts 
  
 = 
  
 { 
  
 HEADLINE 
  
 = 
>  
 3 
 , 
  
 LONG_HEADLINE 
  
 = 
>  
 1 
 , 
  
 DESCRIPTION 
  
 = 
>  
 2 
 , 
  
 BUSINESS_NAME 
  
 = 
>  
 1 
 , 
 }; 
 my 
  
 $min_required_image_asset_counts 
  
 = 
  
 { 
  
 MARKETING_IMAGE 
  
 = 
>  
 1 
 , 
  
 SQUARE_MARKETING_IMAGE 
  
 = 
>  
 1 
 , 
  
 LOGO 
  
 = 
>  
 1 
 , 
 }; 
 # Texts and URLs used to create text and image assets when the TravelAssetSuggestionService 
 # doesn't return enough assets required for creating an asset group. 
 my 
  
 $default_text_assets_info 
  
 = 
  
 { 
  
 HEADLINE 
  
 = 
>  
 [ 
 'Hotel' 
 , 
  
 'Travel Reviews' 
 , 
  
 'Book travel' 
 ], 
  
 LONG_HEADLINE 
  
 = 
>  
 [ 
 'Travel the World' 
 ], 
  
 DESCRIPTION 
  
 = 
>  
 [ 
 'Great deal for your beloved hotel' 
 , 
  
 'Best rate guaranteed' 
 ,], 
  
 BUSINESS_NAME 
  
 = 
>  
 [ 
 'Interplanetary Cruises' 
 ], 
 }; 
 my 
  
 $default_image_assets_info 
  
 = 
  
 { 
  
 MARKETING_IMAGE 
  
 = 
>  
 [ 
 'https://gaagl.page.link/Eit5' 
 ], 
  
 SQUARE_MARKETING_IMAGE 
  
 = 
>  
 [ 
 'https://gaagl.page.link/bjYi' 
 ], 
  
 LOGO 
  
 = 
>  
 [ 
 'https://gaagl.page.link/bjYi' 
 ], 
 }; 
 # 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. 
 use 
  
 constant 
  
 ASSET_TEMPORARY_ID 
  
 = 
>  
 - 
 1 
 ; 
 use 
  
 constant 
  
 BUDGET_TEMPORARY_ID 
  
 = 
>  
 - 
 2 
 ; 
 use 
  
 constant 
  
 CAMPAIGN_TEMPORARY_ID 
  
 = 
>  
 - 
 3 
 ; 
 use 
  
 constant 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 = 
>  
 - 
 4 
 ; 
 # There are also entities that will be created in the same request but do not need to be fixed 
 # temporary IDs because they are referenced only once. 
 our 
  
 $next_temp_id 
  
 = 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 - 
  
 1 
 ; 
 sub 
  
 add_performance_max_for_travel_goals_campaign 
  
 { 
  
 my 
  
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $place_id 
 ) 
  
 = 
  
 @_ 
 ; 
  
 my 
  
 $hotel_asset_suggestion 
  
 = 
  
 get_hotel_asset_suggestion 
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $place_id 
 ); 
  
 # Performance Max campaigns require that repeated assets such as headlines 
  
 # and descriptions be created before the campaign. 
  
 # For the list of required assets for a Performance Max campaign, see 
  
 # https://developers.google.com/google-ads/api/docs/performance-max/assets. 
  
 # 
  
 # This step is the same for any types of Performance Max campaigns. 
  
 # Create the headlines using the hotel asset suggestion. 
  
 my 
  
 $headline_asset_resource_names 
  
 = 
  
 create_multiple_text_assets 
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 HEADLINE 
 , 
  
 $hotel_asset_suggestion 
 ); 
  
 my 
  
 $description_asset_resource_names 
  
 = 
  
 create_multiple_text_assets 
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 DESCRIPTION 
 , 
  
 $hotel_asset_suggestion 
 ); 
  
 # Create a hotel property asset set, which will be used later to link with a newly created 
  
 # campaign. 
  
 my 
  
 $hotel_property_asset_set_resource_name 
  
 = 
  
 create_hotel_asset_set 
 ( 
 $api_client 
 , 
  
 $customer_id 
 ); 
  
 # Create a hotel property asset and link it with the previously created hotel property 
  
 # asset set. This asset will also be linked to an asset group in the later steps. 
  
 # In the real-world scenario, you'd need to create many assets for all your hotel 
  
 # properties. We use one hotel property here for simplicity. 
  
 # Both asset and asset set need to be created before creating a campaign, so we cannot 
  
 # bundle them with other mutate operations below. 
  
 my 
  
 $hotel_property_asset_resource_name 
  
 = 
  
 create_hotel_asset 
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $place_id 
 , 
  
 $hotel_property_asset_set_resource_name 
 ); 
  
 # It's important to create the below entities in this order because they depend on 
  
 # each other. 
  
 # The below methods create and return mutate operations that we later provide to the 
  
 # GoogleAdsService.Mutate method in order to create the entities in a single request. 
  
 # Since the entities for a Performance Max campaign are closely tied to one-another, it's 
  
 # considered a best practice to create them in a single Mutate request so they all complete 
  
 # successfully or fail entirely, leaving no orphaned entities. See: 
  
 # https://developers.google.com/google-ads/api/docs/mutating/overview. 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 push 
  
 @$operations 
 , 
  
 create_campaign_budget_operation 
 ( 
 $customer_id 
 ); 
  
 push 
  
 @$operations 
 , 
  
 create_campaign_operation 
 ( 
 $customer_id 
 , 
  
 $hotel_property_asset_set_resource_name 
 ); 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
  
 create_asset_group_operations 
 ( 
  
 $customer_id 
 , 
  
 $hotel_property_asset_resource_name 
 , 
  
 $headline_asset_resource_names 
 , 
  
 $description_asset_resource_names 
 , 
  
 $hotel_asset_suggestion 
  
 )}; 
  
 # Issue a mutate request to create everything and print its information. 
  
 my 
  
 $mutate_google_ads_response 
  
 = 
  
 $api_client 
 - 
> GoogleAdsService 
 () 
 - 
> mutate 
 ({ 
  
 customerId 
  
 = 
>  
 $customer_id 
 , 
  
 mutateOperations 
  
 = 
>  
 $operations 
  
 }); 
  
 printf 
 "Created the following entities for a campaign budget, a campaign, and an asset group" 
  
 . 
  
 " for Performance Max for travel goals:\n" 
 ; 
  
 print_response_details 
 ( 
 $mutate_google_ads_response 
 ); 
 } 
 # Return hotel asset suggestion obtained from TravelAssetsSuggestionService. 
 sub 
  
 get_hotel_asset_suggestion 
  
 { 
  
 my 
  
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $place_id 
 ) 
  
 = 
  
 @_ 
 ; 
  
 # Send a request to suggest assets to be created as an asset group for the Performance Max 
  
 # for travel goals campaign. 
  
 my 
  
 $suggest_travel_assets_response 
  
 = 
  
 $api_client 
 - 
> TravelAssetSuggestionService 
 () 
 - 
> suggest_travel_assets 
 ({ 
  
 customerId 
  
 = 
>  
 $customer_id 
 , 
  
 # Uses 'en-US' as an example. It can be any language specifications in BCP 47 format. 
  
 languageOption 
  
 = 
>  
 'en-US' 
 , 
  
 # The service accepts several place IDs. We use only one here for demonstration. 
  
 placeIds 
  
 = 
>  
 [ 
 $place_id 
 ], 
  
 }); 
  
 printf 
  
 "Fetched a hotel asset suggestion for the place ID '%s'.\n" 
 , 
  
 $place_id 
 ; 
  
 return 
  
 $suggest_travel_assets_response 
 - 
> { 
 hotelAssetSuggestions 
 }[ 
 0 
 ]; 
 } 
 # Create multiple text assets and returns the list of resource names. The hotel asset 
 # suggestion is used to create a text asset first. If the number of created text assets is 
 # still fewer than the minimum required number of assets of the specified asset field type, 
 # adds more text assets to fulfill the requirement. 
 sub 
  
 create_multiple_text_assets 
  
 { 
  
 my 
  
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $asset_field_type 
 , 
  
 $hotel_asset_suggestion 
 ) 
  
 = 
  
 @_ 
 ; 
  
 # We use the GoogleAdService to create multiple text assets in a single request. 
  
 # First, add all the text assets of the specified asset field type. 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 if 
  
 ( 
 $hotel_asset_suggestion 
 - 
> { 
 status 
 } 
  
 eq 
  
 SUCCESS 
 ) 
  
 { 
  
 foreach 
  
 my 
  
 $text_asset 
  
 ( 
 @ 
 { 
 $hotel_asset_suggestion 
 - 
> { 
 textAssets 
 }}) 
  
 { 
  
 if 
  
 ( 
 $text_asset 
 - 
> { 
 assetFieldType 
 } 
  
 ne 
  
 $asset_field_type 
 ) 
  
 { 
  
 next 
 ; 
  
 } 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
  
 - 
> new 
 ({ 
  
 assetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetService:: 
 AssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Asset 
 - 
> new 
 ({ 
  
 textAsset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 TextAsset 
 - 
> new 
 ({ 
  
 text 
  
 = 
>  
 $text_asset 
 - 
> { 
 text 
 }})})})}); 
  
 } 
  
 } 
  
 # If the added assets are still less than the minimum required assets for the asset field 
  
 # type, add more text assets using the default texts. 
  
 my 
  
 $min_count 
  
 = 
  
 $min_required_text_asset_counts 
 - 
> { 
 $asset_field_type 
 }; 
  
 my 
  
 $num_operations_added 
  
 = 
  
 scalar 
  
 @$operations 
 ; 
  
 for 
  
 ( 
 my 
  
 $i 
  
 = 
  
 0 
  
 ; 
  
 $i 
 < 
 $min_count 
  
 - 
  
 $num_operations_added 
  
 ; 
  
 $i 
 ++ 
 ) 
  
 { 
  
 my 
  
 $text 
  
 = 
  
 $default_text_assets_info 
 - 
> { 
 $asset_field_type 
 }[ 
 $i 
 ++ 
 ]; 
  
 # Creates a mutate operation for a text asset, using the default text. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
  
 - 
> new 
 ({ 
  
 assetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetService:: 
 AssetOperation 
 - 
>  
 new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Asset 
 - 
> new 
 ({ 
  
 textAsset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 TextAsset 
 - 
> new 
 ({ 
  
 text 
  
 = 
>  
 $text 
  
 })})})}); 
  
 } 
  
 # Issue a mutate request to add all assets. 
  
 my 
  
 $mutate_google_ads_response 
  
 = 
  
 $api_client 
 - 
> GoogleAdsService 
 () 
 - 
> mutate 
 ({ 
  
 customerId 
  
 = 
>  
 $customer_id 
 , 
  
 mutateOperations 
  
 = 
>  
 $operations 
  
 }); 
  
 my 
  
 $asset_resource_names 
  
 = 
  
 [] 
 ; 
  
 foreach 
  
 my 
  
 $response 
  
 ( 
 @ 
 { 
 $mutate_google_ads_response 
 - 
> { 
 mutateOperationResponses 
 }}) 
  
 { 
  
 push 
  
 @$asset_resource_names 
 , 
  
 $response 
 - 
> { 
 assetResult 
 }{ 
 resourceName 
 }; 
  
 } 
  
 print_response_details 
 ( 
 $mutate_google_ads_response 
 ); 
  
 return 
  
 $asset_resource_names 
 ; 
 } 
 # Create a hotel property asset set. 
 sub 
  
 create_hotel_asset_set 
  
 { 
  
 my 
  
 ( 
 $api_client 
 , 
  
 $customer_id 
 ) 
  
 = 
  
 @_ 
 ; 
  
 my 
  
 $asset_set_operation 
  
 = 
  
 Google::Ads::GoogleAds::V23::Services::AssetSetService:: 
 AssetSetOperation 
 - 
>  
 new 
 ({ 
  
 # Creates a hotel property asset set. 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetSet 
 - 
> new 
 ({ 
  
 name 
  
 = 
>  
 'My Hotel propery asset set #' 
  
 . 
  
 uniqid 
 (), 
  
 type 
  
 = 
>  
 HOTEL_PROPERTY 
  
 })}); 
  
 # Issues a mutate request to add a hotel asset set and prints its information. 
  
 my 
  
 $response 
  
 = 
  
 $api_client 
 - 
> AssetSetService 
 () 
 - 
> mutate 
 ({ 
  
 customerId 
  
 = 
>  
 $customer_id 
 , 
  
 operations 
  
 = 
>  
 [ 
 $asset_set_operation 
 ]}); 
  
 my 
  
 $asset_set_resource_name 
  
 = 
  
 $response 
 - 
> { 
 results 
 }[ 
 0 
 ]{ 
 resourceName 
 }; 
  
 printf 
  
 "Created an asset set with resource name: '%s'.\n" 
 , 
  
 $asset_set_resource_name 
 ; 
  
 return 
  
 $asset_set_resource_name 
 ; 
 } 
 # Create a hotel property asset using the specified place ID. The place ID must belong to 
 # a hotel property. Then, links it to the specified asset set. 
 # 
 # See https://developers.google.com/places/web-service/place-id to search for a hotel place ID. 
 sub 
  
 create_hotel_asset 
  
 { 
  
 my 
  
 ( 
 $api_client 
 , 
  
 $customer_id 
 , 
  
 $place_id 
 , 
  
 $asset_set_resource_name 
 ) 
  
 = 
  
 @_ 
 ; 
  
 # We use the GoogleAdService to create an asset and asset set asset in a single request. 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 my 
  
 $asset_resource_name 
  
 = 
  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
 $customer_id 
 , 
  
 ASSET_TEMPORARY_ID 
 ); 
  
 # Create a mutate operation for a hotel property asset. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetService:: 
 AssetOperation 
 - 
>  
 new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Asset 
 - 
> new 
 ({ 
  
 resourceName 
  
 = 
>  
 $asset_resource_name 
 , 
  
 hotelPropertyAsset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 HotelPropertyAsset 
 - 
> new 
 ({ 
  
 placeId 
  
 = 
>  
 $place_id 
  
 })})})}); 
  
 # Create a mutate operation for an asset set asset. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetSetAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetSetAssetService:: 
 AssetSetAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetSetAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 $asset_resource_name 
 , 
  
 assetSet 
  
 = 
>  
 $asset_set_resource_name 
  
 })})}); 
  
 # Issue a mutate request to create all entities. 
  
 my 
  
 $mutate_google_ads_response 
  
 = 
  
 $api_client 
 - 
> GoogleAdsService 
 () 
 - 
> mutate 
 ({ 
  
 customerId 
  
 = 
>  
 $customer_id 
 , 
  
 mutateOperations 
  
 = 
>  
 $operations 
  
 }); 
  
 printf 
  
 "Created the following entities for the hotel asset:\n" 
 ; 
  
 print_response_details 
 ( 
 $mutate_google_ads_response 
 ); 
  
 # Return the created asset resource name, which will be used later to create an asset 
  
 # group. Other resource names are not used later. 
  
 return 
  
 $mutate_google_ads_response 
 - 
> { 
 mutateOperationResponses 
 }[ 
 0 
 ] 
  
 { 
 assetResult 
 }{ 
 resourceName 
 }; 
 } 
 # Create a mutate operation that creates a new campaign budget. 
 # 
 # A temporary ID will be assigned to this campaign budget so that it can be 
 # referenced by other objects being created in the same mutate request. 
 sub 
  
 create_campaign_budget_operation 
  
 { 
  
 my 
  
 ( 
 $customer_id 
 ) 
  
 = 
  
 @_ 
 ; 
  
 return 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 campaignBudgetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::CampaignBudgetService:: 
 CampaignBudgetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 CampaignBudget 
 - 
> new 
 ( 
  
 { 
  
 # Set a temporary ID in the budget's resource name so it can be 
  
 # referenced by the campaign in later steps. 
  
 resourceName 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 campaign_budget 
 ( 
  
 $customer_id 
 , 
  
 BUDGET_TEMPORARY_ID 
  
 ), 
  
 name 
  
 = 
>  
 "Performance Max for travel goals campaign budget #" 
  
 . 
  
 uniqid 
 (), 
  
 # The budget period already defaults to DAILY. 
  
 amountMicros 
  
 = 
>  
 50000000 
 , 
  
 deliveryMethod 
  
 = 
>  
 STANDARD 
 , 
  
 # A Performance Max campaign cannot use a shared campaign budget. 
  
 explicitlyShared 
  
 = 
>  
 "false" 
 , 
  
 })})}); 
 } 
 # Create a mutate operation that creates a new Performance Max campaign. Links the specified 
 # hotel property asset set to this campaign. 
 # 
 # A temporary ID will be assigned to this campaign so that it can be referenced by other 
 # objects being created in the same mutate request. 
 sub 
  
 create_campaign_operation 
  
 { 
  
 my 
  
 ( 
 $customer_id 
 , 
  
 $hotel_property_asset_set_resource_name 
 ) 
  
 = 
  
 @_ 
 ; 
  
 # Create a mutate operation that creates a campaign operation. 
  
 return 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 campaignOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::CampaignService:: 
 CampaignOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Campaign 
 - 
> new 
 ({ 
  
 # Assign the resource name with a temporary ID. 
  
 resourceName 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 campaign 
 ( 
  
 $customer_id 
 , 
  
 CAMPAIGN_TEMPORARY_ID 
  
 ), 
  
 name 
  
 = 
>  
 "Performance Max for travel goals campaign #'" 
  
 . 
  
 uniqid 
 (), 
  
 # Set the budget using the given budget resource name. 
  
 campaignBudget 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 campaign_budget 
 ( 
  
 $customer_id 
 , 
  
 BUDGET_TEMPORARY_ID 
  
 ), 
  
 # Set the campaign status as PAUSED. The campaign is the only entity in 
  
 # the mutate request that should have its status set. 
  
 status 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Enums::CampaignStatusEnum:: 
 PAUSED 
 , 
  
 # All Performance Max campaigns have an advertisingChannelType of 
  
 # PERFORMANCE_MAX. The advertisingChannelSubType should not be set. 
  
 advertisingChannelType 
  
 = 
>  
 PERFORMANCE_MAX 
 , 
  
 # To create a Performance Max for travel goals campaign, you need to set 
  
 # `hotelPropertyAssetSet`. 
  
 hotelPropertyAssetSet 
  
 = 
>  
 $hotel_property_asset_set_resource_name 
 , 
  
 # Declare whether or not this campaign serves political ads targeting the EU. 
  
 # Valid values are CONTAINS_EU_POLITICAL_ADVERTISING and 
  
 # DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING. 
  
 containsEuPoliticalAdvertising 
  
 = 
>  
 DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING 
 , 
  
 # Bidding strategy must be set directly on the campaign. 
  
 # Setting a portfolio bidding strategy by resource name is not supported. 
  
 # Max Conversion and Max Conversion Value are the only strategies 
  
 # supported for Performance Max campaigns. 
  
 # An optional ROAS (Return on Advertising Spend) can be set for 
  
 # maximizeConversionValue. The ROAS value must be specified as a ratio in 
  
 # the API. It is calculated by dividing "total value" by "total spend". 
  
 # For more information on Max Conversion Value, see the support article: 
  
 # http://support.google.com/google-ads/answer/7684216. 
  
 # A targetRoas of 3.5 corresponds to a 350% return on ad spend. 
  
 maximizeConversionValue 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 MaximizeConversionValue 
 - 
>  
 new 
 ({ 
  
 targetRoas 
  
 = 
>  
 3.5 
  
 })})})}); 
 } 
 # Create a list of mutate operations that create a new asset group, composed of suggested 
 # assets. In case the number of suggested assets is not enough for the requirements, it'll 
 # create more assets to meet the requirement. 
 # 
 # For the list of required assets for a Performance Max campaign, see 
 # https://developers.google.com/google-ads/api/docs/performance-max/assets. 
 sub 
  
 create_asset_group_operations 
  
 { 
  
 my 
  
 ( 
  
 $customer_id 
 , 
  
 $hotel_property_asset_resource_name 
 , 
  
 $headline_asset_resource_names 
 , 
  
 $description_asset_resource_names 
 , 
  
 $hotel_asset_suggestion 
  
 ) 
  
 = 
  
 @_ 
 ; 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 # Create a new mutate operation that creates an asset group using suggested information 
  
 # when available. 
  
 my 
  
 $asset_group_name 
  
 = 
  
 $hotel_asset_suggestion 
 - 
> { 
 status 
 } 
  
 eq 
  
 SUCCESS 
  
 ? 
  
 $hotel_asset_suggestion 
 - 
> { 
 hotelName 
 } 
  
 : 
  
 'Performance Max for travel goals asset group #' 
  
 . 
  
 uniqid 
 (); 
  
 my 
  
 $asset_group_final_urls 
  
 = 
  
 $hotel_asset_suggestion 
 - 
> { 
 status 
 } 
  
 eq 
  
 SUCCESS 
  
 ? 
  
 [ 
 $hotel_asset_suggestion 
 - 
> { 
 finalUrl 
 }] 
  
 : 
  
 [ 
 'http://www.example.com' 
 ]; 
  
 my 
  
 $asset_group_resource_name 
  
 = 
  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset_group 
 ( 
 $customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
 ); 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetGroupOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupService:: 
 AssetGroupOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroup 
 - 
> new 
 ({ 
  
 resourceName 
  
 = 
>  
 $asset_group_resource_name 
 , 
  
 name 
  
 = 
>  
 $asset_group_name 
 , 
  
 campaign 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 campaign 
 ( 
  
 $customer_id 
 , 
  
 CAMPAIGN_TEMPORARY_ID 
  
 ), 
  
 finalUrls 
  
 = 
>  
 $asset_group_final_urls 
 , 
  
 status 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Enums::AssetGroupStatusEnum:: 
 PAUSED 
  
 })})}); 
  
 # An asset group is linked to an asset by creating a new asset group asset and providing: 
  
 # -  the resource name of the asset group 
  
 # -  the resource name of the asset 
  
 # -  the field_type of the asset in this asset group 
  
 # 
  
 # To learn more about asset groups, see 
  
 # https://developers.google.com/google-ads/api/docs/performance-max/asset-groups. 
  
 # 
  
 # Headline and description assets were created at the first step of this example. So, we 
  
 # just need to link them with the created asset group. 
  
 # 
  
 # Link the headline assets to the asset group. 
  
 foreach 
  
 my 
  
 $resource_name 
  
 ( 
 @$headline_asset_resource_names 
 ) 
  
 { 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
  
 - 
> new 
 ({ 
  
 assetGroupAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService:: 
 AssetGroupAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroupAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 $resource_name 
 , 
  
 assetGroup 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset_group 
 ( 
  
 $customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 ), 
  
 fieldType 
  
 = 
>  
 HEADLINE 
  
 })})}); 
  
 } 
  
 # Link the description assets. 
  
 foreach 
  
 my 
  
 $resource_name 
  
 ( 
 @$description_asset_resource_names 
 ) 
  
 { 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
  
 - 
> new 
 ({ 
  
 assetGroupAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService:: 
 AssetGroupAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroupAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 $resource_name 
 , 
  
 assetGroup 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset_group 
 ( 
  
 $customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 ), 
  
 fieldType 
  
 = 
>  
 DESCRIPTION 
  
 })})}); 
  
 } 
  
 # Link the previously created hotel property asset to the asset group. In the real-world 
  
 # scenario, you'd need to do this step several times for each hotel property asset. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetGroupAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService:: 
 AssetGroupAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroupAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 $hotel_property_asset_resource_name 
 , 
  
 assetGroup 
  
 = 
>  
 $asset_group_resource_name 
 , 
  
 fieldType 
  
 = 
>  
 HOTEL_PROPERTY 
  
 })})}); 
  
 # Create the rest of required text assets and link them to the asset group. 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
 create_text_assets_for_asset_group 
 ( 
 $customer_id 
 , 
  
 $hotel_asset_suggestion 
 ) 
  
 }; 
  
 # Create the image assets and link them to the asset group. Some optional image assets 
  
 # suggested by the TravelAssetSuggestionService might be created too. 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
 create_image_assets_for_asset_group 
 ( 
 $customer_id 
 , 
  
 $hotel_asset_suggestion 
 ) 
  
 }; 
  
 if 
  
 ( 
 $hotel_asset_suggestion 
 - 
> { 
 status 
 } 
  
 eq 
  
 SUCCESS 
 ) 
  
 { 
  
 # Create a new mutate operation for a suggested call-to-action asset and link it 
  
 # to the asset group. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
  
 - 
> new 
 ({ 
  
 assetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetService:: 
 AssetOperation 
 - 
>  
 new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Asset 
 - 
> new 
 ({ 
  
 resourceName 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
  
 $customer_id 
 , 
  
 $next_temp_id 
  
 ), 
  
 name 
  
 = 
>  
 'Suggested call-to-action asset #' 
  
 . 
  
 uniqid 
 (), 
  
 callToActionAsset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 CallToActionAsset 
 - 
> new 
 ({ 
  
 callToAction 
  
 = 
>  
 $hotel_asset_suggestion 
 - 
> { 
 callToAction 
 }})})} 
  
 )}); 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
  
 - 
> new 
 ({ 
  
 assetGroupAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService:: 
 AssetGroupAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroupAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
  
 $customer_id 
 , 
  
 $next_temp_id 
  
 ), 
  
 assetGroup 
  
 = 
>  
 $asset_group_resource_name 
 , 
  
 fieldType 
  
 = 
>  
 CALL_TO_ACTION_SELECTION 
  
 })})}); 
  
 $next_temp_id 
 -- 
 ; 
  
 } 
  
 return 
  
 $operations 
 ; 
 } 
 # Create text assets required for an asset group using the suggested hotel text assets. It adds 
 # more text assets to fulfill the requirements if the suggested hotel text assets are not enough. 
 sub 
  
 create_text_assets_for_asset_group 
  
 { 
  
 my 
  
 ( 
 $customer_id 
 , 
  
 $hotel_asset_suggestion 
 ) 
  
 = 
  
 @_ 
 ; 
  
 # Create mutate operations for the suggested text assets except for headlines and 
  
 # descriptions, which were created previously. 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 # Create a map of asset field type to number of text values. 
  
 my 
  
 $required_text_asset_counts 
  
 = 
  
 {}; 
  
 foreach 
  
 my 
  
 $field_type 
  
 ( 
 keys 
  
 %$min_required_text_asset_counts 
 ) 
  
 { 
  
 $required_text_asset_counts 
 - 
> { 
 $field_type 
 } 
  
 = 
  
 0 
 ; 
  
 } 
  
 if 
  
 ( 
 $hotel_asset_suggestion 
 - 
> { 
 status 
 } 
  
 eq 
  
 SUCCESS 
 ) 
  
 { 
  
 # Add text values of suggested text assets. 
  
 foreach 
  
 my 
  
 $hotel_text_asset 
  
 ( 
 @ 
 { 
 $hotel_asset_suggestion 
 - 
> { 
 textAssetsList 
 }}) 
  
 { 
  
 my 
  
 $asset_field_type 
  
 = 
  
 $hotel_text_asset 
 - 
> { 
 assetFieldType 
 }; 
  
 if 
  
 ( 
 $asset_field_type 
  
 eq 
  
 HEADLINE 
  
 or 
  
 $asset_field_type 
  
 eq 
  
 DESCRIPTION 
 ) 
  
 { 
  
 # Headlines and descriptions were already created at the first step of this code example. 
  
 next 
 ; 
  
 } 
  
 printf 
 "A text asset with text '%s' is suggested for the asset field type '%s'.\n" 
 , 
  
 $hotel_text_asset 
 - 
> { 
 text 
 }, 
  
 $asset_field_type 
 ; 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
  
 create_text_asset_and_asset_group_asset_operations 
 ( 
  
 $customer_id 
 , 
  
 $hotel_text_asset 
 - 
> { 
 text 
 }, 
  
 $hotel_text_asset 
 - 
> { 
 assetFieldType 
 })}; 
  
 $required_text_asset_counts 
 - 
> { 
 $asset_field_type 
 } 
 ++ 
 ; 
  
 } 
  
 } 
  
 # Add more text values by field type to fulfill the requirements. 
  
 foreach 
  
 my 
  
 $asset_field_type 
  
 ( 
 keys 
  
 %$min_required_text_asset_counts 
 ) 
  
 { 
  
 if 
  
 ( 
 $asset_field_type 
  
 eq 
  
 HEADLINE 
  
 or 
  
 $asset_field_type 
  
 eq 
  
 DESCRIPTION 
 ) 
  
 { 
  
 # Headlines and descriptions were already created at the first step of this code example. 
  
 next 
 ; 
  
 } 
  
 my 
  
 $min_count 
  
 = 
  
 $min_required_text_asset_counts 
 - 
> { 
 $asset_field_type 
 }; 
  
 for 
  
 ( 
  
 my 
  
 $i 
  
 = 
  
 0 
  
 ; 
  
 $i 
 < 
 $min_count 
  
 - 
  
 $required_text_asset_counts 
 - 
> { 
 $asset_field_type 
 } 
  
 ; 
  
 $i 
 ++ 
  
 ) 
  
 { 
  
 my 
  
 $text_from_defaults 
  
 = 
  
 $default_text_assets_info 
 - 
> { 
 $asset_field_type 
 }[ 
 $i 
 ++ 
 ]; 
  
 printf 
 "A default text '%s' is used to create a text asset for the asset field type '%s'.\n" 
 , 
  
 $text_from_defaults 
 , 
  
 $asset_field_type 
 ; 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
  
 create_text_asset_and_asset_group_asset_operations 
 ( 
 $customer_id 
 , 
  
 $text_from_defaults 
 , 
  
 $asset_field_type 
 )}; 
  
 } 
  
 } 
  
 return 
  
 $operations 
 ; 
 } 
 # Create image assets required for an asset group using the suggested hotel image assets. It 
 # adds more image assets to fulfill the requirements if the suggested hotel image assets are 
 # not enough. 
 sub 
  
 create_image_assets_for_asset_group 
  
 { 
  
 my 
  
 ( 
 $customer_id 
 , 
  
 $hotel_asset_suggestion 
 ) 
  
 = 
  
 @_ 
 ; 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 # Create mutate operations for the suggested image assets. 
  
 # Create a map of asset field type to number of text values. 
  
 my 
  
 $required_image_asset_counts 
  
 = 
  
 {}; 
  
 foreach 
  
 my 
  
 $field_type 
  
 ( 
 keys 
  
 %$min_required_image_asset_counts 
 ) 
  
 { 
  
 $required_image_asset_counts 
 - 
> { 
 $field_type 
 } 
  
 = 
  
 0 
 ; 
  
 } 
  
 foreach 
  
 my 
  
 $hotel_image_asset 
  
 ( 
 @ 
 { 
 $hotel_asset_suggestion 
 - 
> { 
 imageAssets 
 }}) 
  
 { 
  
 printf 
 "An image asset with url '%s' is suggested for the asset field type '%s'.\n" 
 , 
  
 $hotel_image_asset 
 - 
> { 
 uri 
 }, 
  
 $hotel_image_asset 
 - 
> { 
 assetFieldType 
 }; 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
  
 create_image_asset_and_asset_group_asset_operations 
 ( 
  
 $customer_id 
 , 
  
 $hotel_image_asset 
 - 
> { 
 uri 
 }, 
  
 $hotel_image_asset 
 - 
> { 
 assetFieldType 
 }, 
  
 'Suggested image asset #' 
  
 . 
  
 uniqid 
 ())}; 
  
 # Keeps track of only required image assets. The service may sometimes suggest optional 
  
 # image assets. 
  
 if 
  
 ( 
  
 exists 
  
 $required_image_asset_counts 
 - 
>  
 { 
 $hotel_image_asset 
 - 
> { 
 assetFieldType 
 }}) 
  
 { 
  
 $required_image_asset_counts 
 - 
> { 
 $hotel_image_asset 
 - 
> { 
 assetFieldType 
 }} 
 ++ 
 ; 
  
 } 
  
 } 
  
 # Add more image assets to fulfill the requirements. 
  
 foreach 
  
 my 
  
 $asset_field_type 
  
 ( 
 keys 
  
 %$min_required_image_asset_counts 
 ) 
  
 { 
  
 my 
  
 $min_count 
  
 = 
  
 $min_required_image_asset_counts 
 - 
> { 
 $asset_field_type 
 }; 
  
 for 
  
 ( 
  
 my 
  
 $i 
  
 = 
  
 0 
  
 ; 
  
 $i 
 < 
 $min_count 
  
 - 
  
 $required_image_asset_counts 
 - 
> { 
 $asset_field_type 
 } 
  
 ; 
  
 $i 
 ++ 
  
 ) 
  
 { 
  
 my 
  
 $image_from_defaults 
  
 = 
  
 $default_image_assets_info 
 - 
> { 
 $asset_field_type 
 }[ 
 $i 
 ++ 
 ]; 
  
 printf 
 "A default image URL '%s' is used to create an image asset for the asset field type '%s'.\n" 
 , 
  
 $image_from_defaults 
 , 
  
 $asset_field_type 
 ; 
  
 push 
  
 @$operations 
 , 
  
 @ 
 { 
  
 create_image_asset_and_asset_group_asset_operations 
 ( 
  
 $customer_id 
 , 
  
 $image_from_defaults 
 , 
  
 $asset_field_type 
 , 
  
 lc 
  
 $asset_field_type 
  
 . 
  
 uniqid 
 ())}; 
  
 } 
  
 } 
  
 return 
  
 $operations 
 ; 
 } 
 # Create a list of mutate operations that create a new linked text asset. 
 sub 
  
 create_text_asset_and_asset_group_asset_operations 
  
 { 
  
 my 
  
 ( 
 $customer_id 
 , 
  
 $text 
 , 
  
 $field_type 
 ) 
  
 = 
  
 @_ 
 ; 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 # Create a new mutate operation that creates a text asset. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetService:: 
 AssetOperation 
 - 
>  
 new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Asset 
 - 
> new 
 ({ 
  
 resourceName 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
  
 $customer_id 
 , 
  
 $next_temp_id 
  
 ), 
  
 textAsset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 TextAsset 
 - 
> new 
 ({ 
  
 text 
  
 = 
>  
 $text 
  
 })})})}); 
  
 # Create an asset group asset to link the asset to the asset group. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetGroupAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService:: 
 AssetGroupAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroupAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
  
 $customer_id 
 , 
  
 $next_temp_id 
  
 ), 
  
 assetGroup 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset_group 
 ( 
  
 $customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 ), 
  
 fieldType 
  
 = 
>  
 $field_type 
  
 })})}); 
  
 $next_temp_id 
 -- 
 ; 
  
 return 
  
 $operations 
 ; 
 } 
 # Create a list of mutate operations that create a new linked image asset. 
 sub 
  
 create_image_asset_and_asset_group_asset_operations 
  
 { 
  
 my 
  
 ( 
 $customer_id 
 , 
  
 $url 
 , 
  
 $field_type 
 , 
  
 $asset_name 
 ) 
  
 = 
  
 @_ 
 ; 
  
 my 
  
 $operations 
  
 = 
  
 [] 
 ; 
  
 # Create a new mutate operation that creates an image asset. 
  
 # Create a new mutate operation that creates a text asset. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetService:: 
 AssetOperation 
 - 
>  
 new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 Asset 
 - 
> new 
 ({ 
  
 resourceName 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
  
 $customer_id 
 , 
  
 $next_temp_id 
  
 ), 
  
 # Provide a unique friendly name to identify your asset. 
  
 # When there is an existing image asset with the same content but a different 
  
 # name, the new name will be dropped silently. 
  
 name 
  
 = 
>  
 $asset_name 
 , 
  
 imageAsset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Common:: 
 ImageAsset 
 - 
> new 
 ({ 
  
 data 
  
 = 
>  
 get_base64_data_from_url 
 ( 
 $url 
 )})})})}); 
  
 # Create an asset group asset to link the asset to the asset group. 
  
 push 
  
 @$operations 
 , 
  
 Google::Ads::GoogleAds::V23::Services::GoogleAdsService:: 
 MutateOperation 
 - 
>  
 new 
 ({ 
  
 assetGroupAssetOperation 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Services::AssetGroupAssetService:: 
 AssetGroupAssetOperation 
  
 - 
> new 
 ({ 
  
 create 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Resources:: 
 AssetGroupAsset 
 - 
> new 
 ({ 
  
 asset 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset 
 ( 
  
 $customer_id 
 , 
  
 $next_temp_id 
  
 ), 
  
 assetGroup 
  
 = 
>  
 Google::Ads::GoogleAds::V23::Utils::ResourceNames:: 
 asset_group 
 ( 
  
 $customer_id 
 , 
  
 ASSET_GROUP_TEMPORARY_ID 
  
 ), 
  
 fieldType 
  
 = 
>  
 $field_type 
  
 })})}); 
  
 $next_temp_id 
 -- 
 ; 
  
 return 
  
 $operations 
 ; 
 } 
 # 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. 
 sub 
  
 print_response_details 
  
 { 
  
 my 
  
 ( 
 $mutate_google_ads_response 
 ) 
  
 = 
  
 @_ 
 ; 
  
 foreach 
  
 my 
  
 $response 
  
 ( 
 @ 
 { 
 $mutate_google_ads_response 
 - 
> { 
 mutateOperationResponses 
 }}) 
  
 { 
  
 my 
  
 $result_type 
  
 = 
  
 [ 
 keys 
  
 %$response 
 ] 
 - 
> [ 
 0 
 ]; 
  
 printf 
  
 "Created a(n) %s with '%s'.\n" 
 , 
  
 ucfirst 
  
 $result_type 
  
 =~ 
  
 s/Result$// 
 r 
 , 
  
 $response 
 - 
> { 
 $result_type 
 }{ 
 resourceName 
 }; 
  
 } 
 } 
 # 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 
 ); 
 my 
  
 $customer_id 
  
 = 
  
 undef 
 ; 
 my 
  
 $place_id 
  
 = 
  
 undef 
 ; 
 # Parameters passed on the command line will override any parameters set in code. 
 GetOptions 
 ( 
  
 "customer_id=s" 
  
 = 
>  
 \ 
 $customer_id 
 , 
  
 "place_id=s" 
  
 = 
>  
 \ 
 $place_id 
 , 
 ); 
 # 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 
 , 
  
 $place_id 
 ); 
 # Call the example. 
 add_performance_max_for_travel_goals_campaign 
 ( 
 $api_client 
 , 
  
 $customer_id 
  
 =~ 
  
 s/-//g 
 r 
 , 
  
 $place_id 
 ); 
 =pod 
 =head1 NAME 
 add_performance_max_for_travel_goals_campaign 
 =head1 DESCRIPTION 
 This example shows how to create a Performance Max for travel goals campaign. It also uses 
 TravelAssetSuggestionService to fetch suggested assets for creating an asset group. In case 
 there are not enough assets for the asset group (required by Performance Max), this example will 
 create more assets to fulfill the requirements. 
 For more information about Performance Max campaigns, see 
 https://developers.google.com/google-ads/api/docs/performance-max/overview. 
 Prerequisites: 
 - You must have at least one conversion action in the account. For more about conversion actions, 
 see https://developers.google.com/google-ads/api/docs/conversions/overview#conversion_actions. 
 Notes: 
 - This example uses the default customer conversion goals. For an example of setting 
 campaign-specific conversion goals, see shopping_ads/add_performance_max_retail_campaign.pl. 
 - To learn how to create asset group signals, see 
 advanced_operations/add_performance_max_campaign.pl. 
 =head1 SYNOPSIS 
 add_performance_max_for_travel_goals_campaign.pl [options] 
 -help                         Show the help message. 
 -customer_id                  The Google Ads customer ID. 
 -place_id 					  The place ID of a hotel property. A place ID uniquely identifies a place in the Google Places database. See https://developers.google.com/places/web-service/place-id to learn more. 
 =cut 
  
  

curl

Create a Mobile Website
View Site in Mobile | Classic
Share by: