diff --git a/Rakefile b/Rakefile index ca07bf5b4..a5645767e 100644 --- a/Rakefile +++ b/Rakefile @@ -46,10 +46,6 @@ task :copy_third_party_code do |t| `cp third_party/rspec/caller_filter.rb lib/google/ads/google_ads/deprecation.rb` end -task :copy_timeout_overrides do |t| - `cp patches/v3/google_ads_service_client_config.json lib/google/ads/google_ads/v3/services/` -end - -task :copy_code => [:copy_third_party_code, :copy_timeout_overrides] +task :copy_code => [:copy_third_party_code] task :build => [:copy_code, :codegen, :validate_protos] task :test => [:copy_code, :codegen] diff --git a/codegen/main.rb b/codegen/main.rb index e85a0a662..c4568a1a7 100644 --- a/codegen/main.rb +++ b/codegen/main.rb @@ -61,14 +61,13 @@ module Services end end resources = filter_resources_for_google_ads(version, potential_resources) - resources = cleanup_paths(resources) + resources = cleanup_paths(resources, :RESOURCE) resources, operations = filter_resources_into_resources_and_operations(resources) enums = filter_enums_for_google_ads(version, potential_enums) - enums = cleanup_paths(enums) + enums = cleanup_paths(enums, :ENUM) services = filter_services_for_google_ads(version, potential_services) - services = cleanup_paths(services) operations = enhance_operations_with_classes(operations) diff --git a/codegen/spec/filters_spec.rb b/codegen/spec/filters_spec.rb index 8532ae8b5..8a26a2ecc 100644 --- a/codegen/spec/filters_spec.rb +++ b/codegen/spec/filters_spec.rb @@ -1,5 +1,6 @@ require "spec_helper" -require "filters" +require "src/tracepoints" +require "src/filters" RSpec.describe "#filter_resources_for_google_ads" do let(:resource) { double(:resource, msgclass: msgclass) } @@ -71,20 +72,22 @@ let(:service_class) { double(:service_class, name: name) } let(:path) { __FILE__ } + let(:service) { TemplateService.new(service_class, path) } + context "a google ads service" do - let(:name) { "Google::Ads::GoogleAds::V1::BeesService" } + let(:name) { "Google::Ads::GoogleAds::V1::BeesService::Client" } it "keeps the service class" do - expect(filter_services_for_google_ads(:V1, [[service_class, path]])).to eq( - [[service_class, path]] + expect(filter_services_for_google_ads(:V1, [service])).to eq( + [service] ) end context "an operations client" do - let(:name) { "Google::Ads::GoogleAds::V1::BeesService::OperationsClient" } + let(:name) { "Google::Ads::GoogleAds::V1::BeesService::Operations::Client" } it "doesn't keep the service class" do - expect(filter_services_for_google_ads(:V1, [[service_class, path]])).to eq( + expect(filter_services_for_google_ads(:V1, [service])).to eq( [] ) end @@ -95,7 +98,7 @@ let(:name) { "RSpec::Core::ExampleGroup" } it "doesn't keep the service class" do - expect(filter_services_for_google_ads(:V1, [[service_class, path]])).to eq( + expect(filter_services_for_google_ads(:V1, [service])).to eq( [] ) end diff --git a/codegen/spec/spec_helper.rb b/codegen/spec/spec_helper.rb index bb4b5ae33..4f857c9a3 100644 --- a/codegen/spec/spec_helper.rb +++ b/codegen/spec/spec_helper.rb @@ -13,7 +13,7 @@ # it. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration -$: << "./src" +$: << "./" RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest diff --git a/codegen/spec/template_service_spec.rb b/codegen/spec/template_service_spec.rb new file mode 100644 index 000000000..119748fcb --- /dev/null +++ b/codegen/spec/template_service_spec.rb @@ -0,0 +1,41 @@ +require "spec_helper" +require "src/template_service" + +RSpec.describe TemplateService do + subject(:service) { TemplateService.new(gapic_client_class, path) } + + let(:gapic_client_class) { + double( + :klass, + name: "Google::Ads::GoogleAds::V2::Services::GoogleAdsService::Client", + ) + } + + let(:path) { "in the real world this would be a file path" } + + describe "#factory_method_name" do + it "correctly drops _service_client" do + expect(service.factory_method_name).not_to include("_service_client") + end + end + + describe "#rpc_request_type_for" do + let(:input) { double(:input_klass) } + + before do + # require returns true, so let's just stub it here + allow(service).to receive(:require).with(path).and_return(true) + stub_const( + "Google::Ads::GoogleAds::V2::Services::GoogleAdsService::Service", + double( + :fake_service_klass, + rpc_descs: {Search: double(input: input)} + ) + ) + end + + it "finds the right rpc type" do + expect(service.rpc_request_type_for(:search)).to eq(input) + end + end +end diff --git a/codegen/spec/tracepoints_spec.rb b/codegen/spec/tracepoints_spec.rb index be0ab8e8b..d9603da71 100644 --- a/codegen/spec/tracepoints_spec.rb +++ b/codegen/spec/tracepoints_spec.rb @@ -1,5 +1,5 @@ require "spec_helper" -require "tracepoints" +require "src/tracepoints" require "google/protobuf" RSpec.describe "#with_tracepoints" do @@ -51,7 +51,7 @@ end it "writes client classes to the potential_classes array when they are created" do - allow_any_instance_of(TracePoint).to receive(:path).and_return("_client.rb") + allow_any_instance_of(TracePoint).to receive(:path).and_return("foo/client.rb") expect { with_tracepoints( @@ -59,15 +59,15 @@ potential_services: potential_services, potential_enums: potential_enums, ) do - class FooClient + module Foo + class Client + end end end - }.to change { potential_services }.from(be_empty).to([ - [ - have_attributes(name: "FooClient").and(be_an_instance_of(Class)), - "_client.rb", - ], - ]) + }.to change { potential_services }.from(be_empty).to([have_attributes( + name: "Foo::Client", + path: "foo/client.rb", + )]) end def build_proto_resource(resource_name) diff --git a/codegen/src/filters.rb b/codegen/src/filters.rb index 348fc905c..c3f44f219 100644 --- a/codegen/src/filters.rb +++ b/codegen/src/filters.rb @@ -26,11 +26,8 @@ def filter_enums_for_google_ads(version, potential_enums) def filter_services_for_google_ads(version, potential_services) # services are already class objects because the gapic generator wraps # the protobuf descriptors for us. - potential_services.select { |service, _| - [ - service.name.start_with?("Google::Ads::GoogleAds::#{version.to_s.upcase}"), - !service.name.include?("OperationsClient"), - ].all? + potential_services.select { |service| + service.is_suitable_for_template_at_verison?(version) } end @@ -57,9 +54,20 @@ def enhance_operations_with_classes(operations) } end -def cleanup_paths(collection) +def cleanup_path(path, entity_type) + require_path = path.split(/google-ads-ruby.*\/lib\//).last + if entity_type == :SERVICE + split_path = require_path.split("/") + split_path.pop + split_path.join("/") + else + require_path + end +end + +def cleanup_paths(collection, entity_type) collection.map { |(item, path)| - new_path = path.split(/google-ads-ruby.*\/lib\//).last + new_path = cleanup_path(path, entity_type) [item, new_path] } end diff --git a/codegen/src/template_service.rb b/codegen/src/template_service.rb new file mode 100644 index 000000000..b495c5286 --- /dev/null +++ b/codegen/src/template_service.rb @@ -0,0 +1,72 @@ +require 'src/filters' +require "active_support" +require "active_support/core_ext" + +class TemplateService + attr_reader :path + + def initialize(klass, path) + @klass = klass + @path = cleanup_path(path, :SERVICE).freeze + end + + def name + klass.name.dup + end + + def factory_method_name + factory_name.gsub("ServiceClient", "").underscore + end + + def is_suitable_for_template_at_verison?(version) + return false if name.include?("Operations::Client") + return true if name.start_with?("Google::Ads::GoogleAds::#{version}") + + false + end + + def rpc_request_type_for(rpc) + rpc_requests.select { |rpc_name| + rpc_name == rpc + }.values.first.input + end + + def rpc_names + rpc_requests.keys + end + + private + + attr_reader :klass + + def rpc_requests + # pnlpn@ was here on 2019-12-04: + # rpc_descs is a hash with uppercased RPC names typed as symbols, e.g. + # (:Search) so we underscore so that we're safe for ruby method names + Hash[stub_class.rpc_descs.map { |desc, value| + [desc.to_s.underscore.to_sym, value] + }] + end + + def factory_name + name.split("::")[-2..-1].join("") + end + + def stub_class + # pnlpn@ was here on 2019-12-04: + # the new Gapic service type doesn't cleanly expose the request type that + # it needs the request to be, so this method gets it. + + # this stanza goes from the gax generated FooService::Client to + # FooService::Service and requires it. + stub_path = path.gsub("\/client.rb", "_services_pb.rb") + require stub_path + + # drop "Client" off the end of the constant name, and require the "Service", + # which is the stub class generated by the GRPC protoc compiler, this does + # have enough information for us to recover the RPC information + Kernel.const_get( + name.split("::")[0...-1].join("::") + "::Service" + ) + end +end diff --git a/codegen/src/tracepoints.rb b/codegen/src/tracepoints.rb index 6639b2d2b..9755b6966 100644 --- a/codegen/src/tracepoints.rb +++ b/codegen/src/tracepoints.rb @@ -1,9 +1,11 @@ +require 'src/template_service' + def with_tracepoints(potential_resources:, potential_services:, potential_enums:, &blk) # this function invokes the passed block with tracepoints needed to introspect # protobuf class definitions enabled. The block should contain a set of requires # that load either direct Google::Protobuf::* modules # (e.g. Google::Ads::GoogleAds::V1::Resources::*), or generated gapic service - # clients e.g. Google::Ads::GoogleAds::V1::Services::FeedServiceClient). + # clients e.g. Google::Ads::GoogleAds::V1::Services::FeedService::Client). # These tracepoints are generic to all protobufs, and gapic clients, and not # just google ads, we filter to ads specific objects in further functions. @@ -34,8 +36,10 @@ def with_tracepoints(potential_resources:, potential_services:, potential_enums: # the class name ending with "Client" to get exactly gapic client classes # out trace_services = TracePoint.new(:class) { |tp| - if /_client.rb$/ === tp.path && tp.self.name.end_with?("Client") - potential_services << [tp.self, tp.path] + if /\/client.rb$/ === tp.path && tp.self.name.end_with?("Client") + potential_services << TemplateService.new( + tp.self, tp.path + ) end } trace_services.enable diff --git a/codegen/templates/services.rb.erb b/codegen/templates/services.rb.erb index 629f626d7..c5cb3a2b7 100644 --- a/codegen/templates/services.rb.erb +++ b/codegen/templates/services.rb.erb @@ -1,3 +1,4 @@ +require 'google/ads/google_ads/service_wrapper' module Google module Ads module GoogleAds @@ -5,44 +6,43 @@ module Google module <%= version.to_s.camelize %> class Services def initialize( - service_path:, logging_interceptor:, + error_interceptor:, credentials:, metadata:, - exception_transformer:) - @service_path = service_path - @logging_interceptor = logging_interceptor + endpoint:, + deprecation: + ) + @interceptors = [ + error_interceptor, + logging_interceptor, + ].compact @credentials = credentials @metadata = metadata - @exception_transformer = exception_transformer - end - - def have_service_path? - @service_path != nil && !@service_path.empty? + @endpoint = endpoint + @deprecation = deprecation end def have_logging_interceptor? @logging_interceptor != nil end - <% services.each do |service, path| %> - def <%= service.name.split("::").last.underscore.gsub("_service_client", "") %>(&blk) - require "<%= path %>" - cls = Class.new(<%= service.name %>) - if have_service_path? - cls.const_set("SERVICE_PATH", @service_path) - end - - if have_logging_interceptor? - cls.const_set("GRPC_INTERCEPTORS", [@logging_interceptor]) - end - - blk.call(cls) unless blk.nil? - - cls.new( - credentials: @credentials, - metadata: @metadata, - exception_transformer: @exception_transformer, + <% services.each do |service| %> + def <%= service.factory_method_name %>(&blk) + require "<%= service.path %>" + svc = ServiceWrapper.new( + service: <%= service.name %>.new do |config| + config.credentials = @credentials + config.interceptors = @interceptors + config.metadata = @metadata + config.endpoint = @endpoint + end, + rpc_inputs: { + <% service.rpc_names.each do |rpc_name| %> + <%= rpc_name %>: <%= service.rpc_request_type_for(rpc_name) %>, + <% end %> + }, + deprecation: @deprecation ) end <% end %> diff --git a/examples/account_management/create_customer.rb b/examples/account_management/create_customer.rb index d2c982bbd..3087d4b49 100755 --- a/examples/account_management/create_customer.rb +++ b/examples/account_management/create_customer.rb @@ -47,7 +47,10 @@ def create_customer(manager_customer_id) c.has_partners_badge = false end - response = client.service.customer.create_customer_client(manager_customer_id, customer) + response = client.service.customer.create_customer_client( + customer_id: manager_customer_id, + customer_client: customer + ) puts "Created a customer with resource name #{response.resource_name} under" + " the manager account with customer ID #{manager_customer_id}." @@ -103,10 +106,5 @@ def create_customer(manager_customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/account_management/get_account_changes.rb b/examples/account_management/get_account_changes.rb index 5fb50b1ef..eac5a9568 100755 --- a/examples/account_management/get_account_changes.rb +++ b/examples/account_management/get_account_changes.rb @@ -42,7 +42,11 @@ def get_account_changes(customer_id) change_status.last_change_date_time QUERY - response = client.service.google_ads.search(customer_id, query, page_size: PAGE_SIZE) + response = client.service.google_ads.search( + customer_id: customer_id, + query: query, + page_size: PAGE_SIZE + ) response.each do |row| cs = row.change_status @@ -115,10 +119,5 @@ def get_account_changes(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/account_management/get_account_hierarchy.rb b/examples/account_management/get_account_hierarchy.rb new file mode 100755 index 000000000..cd55a0edd --- /dev/null +++ b/examples/account_management/get_account_hierarchy.rb @@ -0,0 +1,204 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example gets the account hierarchy of the specified manager account. +# If you don't specify manager customer ID, the example will instead print the +# hierarchies of all accessible customer accounts for your authenticated +# Google account. +# Note that if the list of accessible customers for your authenticated Google +# account includes accounts within the same hierarchy, this example will +# retrieve and print the overlapping portions of the hierarchy for each +# accessible customer. + +require 'optparse' +require 'google/ads/google_ads' +require 'thread' + +def get_account_hierarchy(manager_customer_id, login_customer_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # Set the specified login customer ID. + client.configure do |config| + if login_customer_id + config.login_customer_id = login_customer_id.to_i + elsif manager_customer_id + config.login_customer_id = manager_customer_id.to_i + end + end + + google_ads_service = client.service.google_ads + + seed_customer_ids = [] + + if manager_customer_id + seed_customer_ids << manager_customer_id + else + puts 'No manager customer ID is specified. The example will print the ' + + 'hierarchies of all accessible customer IDs:' + customer_resource_names = client.service.customer. + list_accessible_customers().resource_names + customer_resource_names.each do |res| + seed_customer_ids << res.split('/')[1] + end + end + + search_query = <<~QUERY + SELECT + customer_client.client_customer, + customer_client.level, + customer_client.manager, + customer_client.descriptive_name, + customer_client.currency_code, + customer_client.time_zone, + customer_client.id + FROM customer_client + WHERE customer_client.level <= 1 + QUERY + + seed_customer_ids.each do |seed_cid| + # Performs a breadth-first search to build a dictionary that maps managers + # to their child accounts (cid_to_children). + unprocessed_customer_ids = Queue.new + unprocessed_customer_ids << seed_cid + cid_to_children = Hash.new { |h, k| h[k] = [] } + root_customer_client = nil + + while unprocessed_customer_ids.size > 0 + cid = unprocessed_customer_ids.pop + response = google_ads_service.search( + customer_id: cid, + query: search_query, + page_size: PAGE_SIZE, + ) + + # Iterates over all rows in all pages to get all customer clients under + # the specified customer's hierarchy. + response.each do |row| + customer_client = row.customer_client + + # The customer client that with level 0 is the specified customer + if customer_client.level.value == 0 + if root_customer_client == nil + root_customer_client = customer_client + end + next + end + + # For all level-1 (direct child) accounts that are a manager account, + # the above query will be run against them to create a dictionary of + # managers mapped to their child accounts for printing the hierarchy + # afterwards. + cid_to_children[cid.to_s] << customer_client + + if customer_client&.manager&.value + if !cid_to_children.key?(customer_client.id.to_s) && + customer_client.level.value == 1 + unprocessed_customer_ids << customer_client.id + end + end + end + end + + if root_customer_client + puts "The hierarychy of customer ID #{root_customer_client.id} " + + "is printed below:" + print_account_hierarchy(root_customer_client, cid_to_children, 0) + else + puts "Customer ID #{manager_customer_id} is likely a test account, " \ + "so its customer client information cannot be retrieved." + end + end +end + +def print_account_hierarchy(customer_client, cid_to_children, depth) + if depth == 0 + puts 'Customer ID (Descriptive Name, Currency Code, Time Zone)' + end + + customer_id = customer_client.id + puts '-' * (depth * 2) + + "#{customer_id} #{customer_client.descriptive_name} " + + "#{customer_client.currency_code} #{customer_client.time_zone}" + + # Recursively call this function for all child accounts of customer_client + if cid_to_children.key?(customer_id.to_s) + cid_to_children[customer_id.to_s].each do |child| + print_account_hierarchy(child, cid_to_children, depth + 1) + end + end +end + +if __FILE__ == $PROGRAM_NAME + PAGE_SIZE = 1000 + + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = nil + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: ruby %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-M', '--manager-customer-id MANAGER-CUSTOMER-ID', String, 'Manager Customer ID (optional)') do |v| + options[:manager_customer_id] = v.tr("-", "") + end + + opts.on('-L', '--login-customer-id LOGIN-CUSTOMER-ID', String, 'Login Customer ID (optional)') do |v| + options[:login_customer_id] = v.tr("-", "") + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + get_account_hierarchy( + options[:manager_customer_id], + options[:login_customer_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("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/account_management/get_account_information.rb b/examples/account_management/get_account_information.rb index 31d16fd25..17ecc1605 100755 --- a/examples/account_management/get_account_information.rb +++ b/examples/account_management/get_account_information.rb @@ -27,14 +27,14 @@ def get_account_information(customer_id) client = Google::Ads::GoogleAds::GoogleAdsClient.new resource_name = client.path.customer(customer_id) - customer = client.service.customer.get_customer(resource_name) + customer = client.service.customer.get_customer(resource_name: resource_name) puts "Customer ID #{customer.id}\n"\ "\tdescriptive_name: #{customer.descriptive_name}\n"\ "\tcurrency_code: #{customer.currency_code}\n"\ "\ttime_zone: #{customer.time_zone}\n"\ "\ttracking_url_template: #{customer.tracking_url_template}\n"\ - "\tauto_tagging_enabled: #{customer.auto_tagging_enabled.value}" + "\tauto_tagging_enabled: #{customer.auto_tagging_enabled}" end if __FILE__ == $PROGRAM_NAME @@ -84,10 +84,5 @@ def get_account_information(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/account_management/link_manager_to_client.rb b/examples/account_management/link_manager_to_client.rb index 4148abdf6..3054f143f 100644 --- a/examples/account_management/link_manager_to_client.rb +++ b/examples/account_management/link_manager_to_client.rb @@ -45,8 +45,8 @@ def link_manager_to_client(manager_customer_id, client_customer_id) client_link_operation = client.operation.create_resource.customer_client_link(client_link) response = client.service.customer_client_link.mutate_customer_client_link( - manager_customer_id, - client_link_operation, + customer_id: manager_customer_id, + operation: client_link_operation, ) client_link_resource_name = response.result.resource_name @@ -65,7 +65,7 @@ def link_manager_to_client(manager_customer_id, client_customer_id) customer_client_link.resource_name = '#{client_link_resource_name}' QUERY - response = client.service.google_ads.search(manager_customer_id, query) + response = client.service.google_ads.search(customer_id: manager_customer_id, query: query) manager_link_id = response.first.customer_client_link.manager_link_id # Accept the link using the client account. @@ -85,8 +85,8 @@ def link_manager_to_client(manager_customer_id, client_customer_id) end response = client.service.customer_manager_link.mutate_customer_manager_link( - client_customer_id, - [manager_link_operation], + customer_id: client_customer_id, + operations: [manager_link_operation], ) puts "Client accepted invitation with resource name " \ @@ -149,10 +149,5 @@ def link_manager_to_client(manager_customer_id, client_customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/account_management/list_accessible_customers.rb b/examples/account_management/list_accessible_customers.rb index 7b751e50d..b9322cb68 100755 --- a/examples/account_management/list_accessible_customers.rb +++ b/examples/account_management/list_accessible_customers.rb @@ -56,10 +56,5 @@ def list_accessible_customers() end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/add_ad_group_bid_modifier.rb b/examples/advanced_operations/add_ad_group_bid_modifier.rb index 3897a9697..a33510454 100644 --- a/examples/advanced_operations/add_ad_group_bid_modifier.rb +++ b/examples/advanced_operations/add_ad_group_bid_modifier.rb @@ -47,7 +47,9 @@ def add_ad_group_bid_modifier(customer_id, ad_group_id, bid_modifier_value) # Add the ad group ad. response = client.service.ad_group_bid_modifier.mutate_ad_group_bid_modifiers( - customer_id, [operation]) + customer_id: customer_id, + operations: [operation] + ) puts "Added #{response.results.size} ad group bid modifiers:" response.results.each do |added_ad_group_bid_modifier| @@ -99,7 +101,7 @@ def add_ad_group_bid_modifier(customer_id, ad_group_id, bid_modifier_value) begin add_ad_group_bid_modifier(options.fetch(:customer_id).tr("-", ""), - options[:ad_group_id], options[:bid_modifier_value]) + options[:ad_group_id], options[:bid_modifier_value].to_f) rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e e.failure.errors.each do |error| STDERR.printf("Error with message: %s\n", error.message) @@ -114,10 +116,5 @@ def add_ad_group_bid_modifier(customer_id, ad_group_id, bid_modifier_value) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/add_app_campaign.rb b/examples/advanced_operations/add_app_campaign.rb new file mode 100755 index 000000000..5509bd38a --- /dev/null +++ b/examples/advanced_operations/add_app_campaign.rb @@ -0,0 +1,261 @@ +#!/usr/bin/env ruby + +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example adds an App campaign. +# +# For guidance regarding App Campaigns, see: +# https://developers.google.com/google-ads/api/docs/app-campaigns/overview +# +# To get campaigns, run basic_operations/get_campaigns.rb. +# To upload image assets for this campaign, run misc/upload_image_asset.rb. + +require 'optparse' +require 'google/ads/google_ads' +require 'date' + +def add_app_campaign(customer_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 the budget for the campaign. + budget_resource_name = create_budget(client, customer_id) + + # Creates the campaign. + campaign_resource_name = create_campaign(client, customer_id, budget_resource_name) + + # Creates campaign targeting. + create_campaign_targeting_criteria(client, customer_id, campaign_resource_name) + + # Creates an Ad Group. + ad_group_resource_name = create_ad_group(client, customer_id, campaign_resource_name) + + # Creates an App Ad. + create_app_ad(client, customer_id, ad_group_resource_name) +end + +def create_budget(client, customer_id) + # Creates a campaign budget. + campaign_budget = client.resource.campaign_budget do |b| + b.name = "Interplanetary Cruise #{(Time.now.to_f * 1000).to_i}" + b.amount_micros = 50_000_000 + b.delivery_method = :STANDARD + # An App campaign cannot use a shared campaign budget. + # explicitly_shared must be set to false. + b.explicitly_shared = false + end + + # Submits the campaign budget operation to add the campaign budget. + operation = client.operation.create_resource.campaign_budget(campaign_budget) + response = client.service.campaign_budget.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation] + ) + puts "Created campaign budget: #{response.results.first.resource_name}" + response.results.first.resource_name +end + +def create_campaign(client, customer_id, budget_resource_name) + campaign = client.resource.campaign do |c| + c.name = "Interplanetary Cruise App #{(Time.now.to_f * 1000).to_i}" + c.campaign_budget = budget_resource_name + # Recommendation: Set the campaign to PAUSED when creating it to + # prevent the ads from immediately serving. Set to ENABLED once you've + # added targeting and the ads are ready to serve. + c.status = :PAUSED + # All App campaigns have an advertising_channel_type of + # MULTI_CHANNEL to reflect the fact that ads from these campaigns are + # eligible to appear on multiple channels. + c.advertising_channel_type = :MULTI_CHANNEL + c.advertising_channel_sub_type = :APP_CAMPAIGN + # Sets the target CPA to $1 / app install. + # + # campaign_bidding_strategy is a 'oneof' message so setting target_cpa + # is mutually exclusive with other bidding strategies such as + # manual_cpc, commission, maximize_conversions, etc. + # See https://developers.google.com/google-ads/api/reference/rpc + # under current version / resources / Campaign + c.target_cpa = client.resource.target_cpa do |target_cpa| + target_cpa.target_cpa_micros = 1_000_000 + end + # Sets the App Campaign Settings. + c.app_campaign_setting = client.resource.app_campaign_setting do |acs| + acs.app_id = 'com.google.android.apps.adwords' + acs.app_store = :GOOGLE_APP_STORE + # Optimize this campaign for getting new users for your app. + acs.bidding_strategy_goal_type = :OPTIMIZE_INSTALLS_TARGET_INSTALL_COST + end + # Optional fields + c.start_date = DateTime.parse((Date.today + 1).to_s).strftime('%Y%m%d') + c.end_date = DateTime.parse(Date.today.next_year.to_s).strftime('%Y%m%d') + end + + operation = client.operation.create_resource.campaign(campaign) + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [operation] + ) + puts "Created campaign: #{response.results.first.resource_name}" + response.results.first.resource_name +end + +def create_campaign_targeting_criteria(client, customer_id, campaign_resource_name) + # Besides using location_id, you can also search by location names from + # GeoTargetConstantService.suggest_geo_target_constants() and directly + # apply GeoTargetConstant.resource_name here. An example can be found + # in targeting/get_geo_target_constant_by_names.rb. + location_ops = [ + '21137', # California + '2484', # Mexico + ].map do |location_id| + client.operation.create_resource.campaign_criterion do |cc| + cc.campaign = campaign_resource_name + cc.location = client.resource.location_info do |li| + li.geo_target_constant = client.path.geo_target_constant(location_id) + end + end + end + + # Creates the language campaign criteria. + language_ops = [ + '1000', # English + '1003', # Spanish + ].map do |language_id| + client.operation.create_resource.campaign_criterion do |cc| + cc.campaign = campaign_resource_name + cc.language = client.resource.language_info do |li| + li.language_constant = client.path.language_constant(language_id) + end + end + end + + operations = location_ops + language_ops + + # Submits the criteria operations. + criteria_service = client.service.campaign_criterion + response = criteria_service.mutate_campaign_criteria( + customer_id: customer_id, + operations: operations + ) + response.results.each do |resource| + puts "Created campaign criterion: #{resource.resource_name}" + end +end + +def create_ad_group(client, customer_id, campaign_resource_name) + ad_group = client.resource.ad_group do |ag| + ag.name = "Earth to Mars Cruises #{(Time.now.to_f * 1000).to_i}" + ag.status = :ENABLED + ag.campaign = campaign_resource_name + end + + operation = client.operation.create_resource.ad_group(ad_group) + response = client.service.ad_group.mutate_ad_groups( + customer_id: customer_id, + operations: [operation] + ) + + puts "Created ad group: #{response.results.first.resource_name}" + response.results.first.resource_name +end + +def create_app_ad(client, customer_id, ad_group_resource_name) + # Creates the ad group ad. + ad_group_ad = client.resource.ad_group_ad do |aga| + aga.status = :ENABLED + aga.ad_group = ad_group_resource_name + # ad_data is a 'oneof' message so setting app_ad + # is mutually exclusive with ad data fields such as + # text_ad, gmail_ad, etc. + aga.ad = client.resource.ad do |ad| + ad.app_ad = client.resource.app_ad_info do |info| + info.headlines << client.resource.ad_text_asset do |ata| + ata.text = 'A cool puzzle game' + end + info.headlines << client.resource.ad_text_asset do |ata| + ata.text = 'Remove connected blocks' + end + info.descriptions << client.resource.ad_text_asset do |ata| + ata.text = '3 difficulty levels' + end + info.descriptions << client.resource.ad_text_asset do |ata| + ata.text = '4 colorful fun skins' + end + # Optional: You can set up to 20 image assets for your campaign. + # info.images << client.resource.ad_image_asset do |aia| + # ala = "[INSERT_AD_IMAGE_RESOURCE_NAME(s)_HERE]" + # end + end + end + end + + operation = client.operation.create_resource.ad_group_ad(ad_group_ad) + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation] + ) + puts "Created ad group ad: #{response.results.first.resource_name}" +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: add_app_campaigns.rb [options]') + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_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_app_campaign(options.fetch(:customer_id).tr('-', '')) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + error.location&.field_path_elements&.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/advanced_operations/add_dynamic_page_feed.rb b/examples/advanced_operations/add_dynamic_page_feed.rb index 0e1285d55..e3a54fb5b 100644 --- a/examples/advanced_operations/add_dynamic_page_feed.rb +++ b/examples/advanced_operations/add_dynamic_page_feed.rb @@ -69,7 +69,10 @@ def create_feed(client, customer_id) operation = client.operation.create_resource.feed(feed) - response = client.service.feed.mutate_feeds(customer_id, [operation]) + response = client.service.feed.mutate_feeds( + customer_id: customer_id, + operations: [operation], + ) feed_id = response.results.first.resource_name # We need to look up the attribute name and IDs for the feed we just created @@ -85,7 +88,10 @@ def create_feed(client, customer_id) LIMIT 100 EOD - response = client.service.google_ads.search(customer_id, query) + response = client.service.google_ads.search( + customer_id: customer_id, + query: query, + ) # Hash.[] takes an aray of pairs and turns them in to a hash with keys # equal to the first item, and values equal to the second item, we get two @@ -127,7 +133,10 @@ def create_feed_mapping(client, customer_id, feed_details) operation = client.operation.create_resource.feed_mapping(feed_mapping) - response = client.service.feed_mapping.mutate_feed_mappings(customer_id, [operation]) + response = client.service.feed_mapping.mutate_feed_mappings( + customer_id: customer_id, + operations: [operation], + ) puts "Feed mapping created with id #{response.results.first.resource_name}" end @@ -157,7 +166,10 @@ def create_feed_items(client, customer_id, feed_details, label) client.operation.create_resource.feed_item(fi) } - response = client.service.feed_item.mutate_feed_items(customer_id, ops) + response = client.service.feed_item.mutate_feed_items( + customer_id: customer_id, + operations: ops, + ) response.results.each do |result| puts "Created feed item with id #{result.resource_name}" @@ -167,14 +179,20 @@ def create_feed_items(client, customer_id, feed_details, label) def update_campaign_dsa_setting(client, customer_id, campaign_id, feed_details) query = <<~EOD SELECT - campaign.id, campaign.name, campaign.dynamic_search_ads_setting.domain_name + campaign.id, + campaign.name, + campaign.dynamic_search_ads_setting.domain_name, + campaign.dynamic_search_ads_setting.language_code FROM campaign WHERE campaign.id = #{campaign_id} LIMIT 1000 EOD - response = client.service.google_ads.search(customer_id, query) + response = client.service.google_ads.search( + customer_id: customer_id, + query: query, + ) result = response.first if result.nil? @@ -183,16 +201,27 @@ def update_campaign_dsa_setting(client, customer_id, campaign_id, feed_details) campaign = result.campaign - if !campaign.dynamic_search_ads_setting || !campaign.dynamic_search_ads_setting.domain_name \ - || campaign.dynamic_search_ads_setting.domain_name.value == "" + if !campaign.dynamic_search_ads_setting \ + || !campaign.dynamic_search_ads_setting.domain_name \ + || campaign.dynamic_search_ads_setting.domain_name == "" \ + || !campaign.dynamic_search_ads_setting.language_code \ + || campaign.dynamic_search_ads_setting.language_code == "" raise "Campaign id #{campaign_id} is not set up for dynamic search ads" end op = client.operation.update_resource.campaign(campaign) do campaign.dynamic_search_ads_setting.feeds << feed_details.resource_name end - - response = client.service.campaign.mutate_campaigns(customer_id, [op]) + # You have to specify these fields on all requests, even if you don't change them. + # By adding them to the update_mask, the API treats them as new values, but we're + # just passing along the old values we selected in the query above. + op.update_mask.paths << "dynamic_search_ads_setting.language_code" + op.update_mask.paths << "dynamic_search_ads_setting.domain_name" + + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [op], + ) puts "Updated campaign #{response.results.first.resource_name}" end @@ -215,7 +244,10 @@ def add_dsa_targeting(client, customer_id, ad_group_resource_name, label) op = client.operation.create_resource.ad_group_criterion(ad_group_criterion) - response = client.service.ad_group_criterion.mutate_ad_group_criteria(customer_id, [op]) + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: [op], + ) puts "Created ad group criterion with id: #{response.results.first.resource_name}" end @@ -281,10 +313,5 @@ def add_dsa_targeting(client, customer_id, ad_group_resource_name, label) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/add_dynamic_search_ads.rb b/examples/advanced_operations/add_dynamic_search_ads.rb index 821c6f77d..9a3a7cda9 100644 --- a/examples/advanced_operations/add_dynamic_search_ads.rb +++ b/examples/advanced_operations/add_dynamic_search_ads.rb @@ -54,8 +54,8 @@ def create_budget(client, customer_id) operation = client.operation.create_resource.campaign_budget(campaign_budget) response = client.service.campaign_budget.mutate_campaign_budgets( - customer_id, - [operation], + customer_id: customer_id, + operations: [operation], ) puts("Created campaign budget with ID: #{response.results.first.resource_name}") @@ -71,22 +71,22 @@ def create_campaign(client, customer_id, budget_resource_name) c.manual_cpc = client.resource.manual_cpc c.campaign_budget = budget_resource_name - c.dynamic_search_ads_setting = client.resource.dynamic_search_ads_setting do |s| s.domain_name = "example.com" s.language_code = "en" end c.start_date = DateTime.parse((Date.today + 1).to_s).strftime('%Y%m%d') - - c.end_date = DateTime.parse(Date.today.next_year.to_s).strftime('%Y%m%d') - end operation = client.operation.create_resource.campaign(campaign) + require 'pry'; binding.pry - response = client.service.campaign.mutate_campaigns(customer_id, [operation]) + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) puts("Created campaign with ID: #{response.results.first.resource_name}") response.results.first.resource_name end @@ -106,7 +106,10 @@ def create_ad_group(client, customer_id, campaign_resource_name) operation = client.operation.create_resource.ad_group(ad_group) - response = client.service.ad_group.mutate_ad_groups(customer_id, [operation]) + response = client.service.ad_group.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) puts("Created ad group with ID: #{response.results.first.resource_name}") response.results.first.resource_name @@ -126,7 +129,10 @@ def create_expanded_dsa(client, customer_id, ad_group_resource_name) operation = client.operation.create_resource.ad_group_ad(ad_group_ad) - response = client.service.ad_group_ad.mutate_ad_group_ads(customer_id, [operation]) + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation], + ) puts("Created ad group ad with ID: #{response.results.first.resource_name}") end @@ -157,8 +163,8 @@ def add_web_page_criteria(client, customer_id, ad_group_resource_name) operation = client.operation.create_resource.ad_group_criterion(criterion) response = client.service.ad_group_criterion.mutate_ad_group_criteria( - customer_id, - [operation], + customer_id: customer_id, + operations: [operation], ) puts("Created ad group criterion with ID: #{response.results.first.resource_name}") end @@ -210,10 +216,5 @@ def add_web_page_criteria(client, customer_id, ad_group_resource_name) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/add_expanded_text_ad_with_upgraded_urls.rb b/examples/advanced_operations/add_expanded_text_ad_with_upgraded_urls.rb index 30f663f72..0c8047b4b 100755 --- a/examples/advanced_operations/add_expanded_text_ad_with_upgraded_urls.rb +++ b/examples/advanced_operations/add_expanded_text_ad_with_upgraded_urls.rb @@ -73,7 +73,9 @@ def add_expanded_text_ad_with_upgraded_urls(customer_id, ad_group_id) # Add the ad group ad. response = client.service.ad_group_ad.mutate_ad_group_ads( - customer_id, [ad_group_ad_operation]) + customer_id: customer_id, + operations:[ad_group_ad_operation] + ) puts "Created expanded text ad #{response.results.first.resource_name}." end @@ -131,10 +133,5 @@ def add_expanded_text_ad_with_upgraded_urls(customer_id, ad_group_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/add_gmail_ad.rb b/examples/advanced_operations/add_gmail_ad.rb index d97d55195..fe1788d0c 100644 --- a/examples/advanced_operations/add_gmail_ad.rb +++ b/examples/advanced_operations/add_gmail_ad.rb @@ -24,7 +24,6 @@ require 'date' require 'open-uri' require 'google/ads/google_ads' -require 'google/ads/google_ads/v1/errors/errors_pb' def add_gmail_ad(customer_id, campaign_id, ad_group_id) client = Google::Ads::GoogleAds::GoogleAdsClient.new @@ -65,8 +64,8 @@ def add_gmail_ad(customer_id, campaign_id, ad_group_id) media_file_marketing_image_op = client.operation.create_resource.media_file(media_file_marketing) response = client.service.media_file.mutate_media_files( - customer_id, - [ + customer_id: customer_id, + operations: [ media_file_logo_op, media_file_marketing_image_op, ] @@ -103,7 +102,10 @@ def add_gmail_ad(customer_id, campaign_id, ad_group_id) op = client.operation.create_resource.ad_group_ad(ad_group_ad) - response = client.service.ad_group_ad.mutate_ad_group_ads(customer_id, [op]) + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [op] + ) puts "Created Gmail Ad with ID #{response.results.first.resource_name}." end @@ -174,10 +176,5 @@ def get_image(url) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/add_smart_display_ad.rb b/examples/advanced_operations/add_smart_display_ad.rb new file mode 100755 index 000000000..a5db4f265 --- /dev/null +++ b/examples/advanced_operations/add_smart_display_ad.rb @@ -0,0 +1,295 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example adds a Smart Display campaign, an ad group and a +# responsive display ad. +# More information about Smart Display campaigns can be found at +# https://support.google.com/google-ads/answer/7020281. +# +# IMPORTANT: The AssetService requires you to reuse what you've uploaded +# previously. Therefore, you cannot create an image asset with the exactly same +# bytes. In case you want to run this example more than once, note down the +# created assets' resource names and specify them as command-line arguments for +# marketing and square marketing images. +# +# Alternatively, you can modify the image URLs' constants directly to use other +# images. You can find image specifications in the +# responsive_display_ad_info.rb file. + +require 'optparse' +require 'date' +require 'open-uri' +require 'google/ads/google_ads' + +def add_smart_display_ad( + customer_id, + marketing_image_asset_resource_name = nil, + square_marketing_image_asset_resource_name = nil +) + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + budget = create_campaign_budget(client, customer_id) + campaign = create_smart_display_campaign(client, customer_id, budget) + ad_group = create_ad_group(client, customer_id, campaign) + create_responsive_display_ad( + client, + customer_id, + ad_group, + marketing_image_asset_resource_name, + square_marketing_image_asset_resource_name, + ) +end + +def create_campaign_budget(client, customer_id) + # Creates a campaign budget operation. + operation = client.operation.create_resource.campaign_budget do |b| + b.name = "Interplanetary Cruise Budget #{(Time.new.to_f * 1000).to_i}" + b.delivery_method = :STANDARD + b.amount_micros = 5_000_000 + end + + # Issues a mutate request to add campaign budgets. + response = client.service.campaign_budget.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) + + # Prints out some information about the created campaign budget. + resource_name = response.results.first.resource_name + puts "Created campaign budget #{resource_name}" + + resource_name +end + +def create_smart_display_campaign(client, customer_id, budget) + # Creates a campaign operation. + operation = client.operation.create_resource.campaign do |c| + c.name = "Smart Display Campaign #{(Time.new.to_f * 1000).to_i}" + # Smart Display campaign requires the advertising_channel_type as 'DISPLAY'. + c.advertising_channel_type = :DISPLAY + # Smart Display campaign requires the advertising_channel_sub_type as + # 'DISPLAY_SMART_CAMPAIGN'. + c.advertising_channel_sub_type = :DISPLAY_SMART_CAMPAIGN + # Smart Display campaign requires the TargetCpa bidding strategy. + c.target_cpa = client.resource.target_cpa do |tcpa| + tcpa.target_cpa_micros = 5_000_000 + end + c.campaign_budget = budget + # Optional: Sets the start and end dates for the campaign, beginning one day + # from now and ending a month from now. + c.start_date = DateTime.parse((Date.today + 1).to_s).strftime('%Y%m%d') + c.end_date = DateTime.parse(Date.today.next_month.to_s).strftime('%Y%m%d') + end + + # Issues a mutate request to add the campaign. + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) + + # Prints out some information about the created campaign. + resource_name = response.results.first.resource_name + puts "Added a smart display campaign named #{resource_name}" + + resource_name +end + +def create_ad_group(client, customer_id, campaign) + # Creates an ad group operation. + operation = client.operation.create_resource.ad_group do |ag| + ag.name = "Earth to Mars Cruises #{(Time.new.to_f * 1000).to_i}" + ag.campaign = campaign + ag.status = :PAUSED + end + + # Issues a mutate request to add the ad group. + response = client.service.ad_group.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) + + # Print out some information about the added ad group. + resource_name = response.results.first.resource_name + puts "Added ad group named #{resource_name}" + + resource_name +end + +def create_responsive_display_ad( + client, + customer_id, + ad_group, + marketing_image_asset_resource_name, + square_marketing_image_asset_resource_name +) + marketing_image_url = 'https://goo.gl/3b9Wfh' + square_marketing_image_url = 'https://goo.gl/mtt54n' + + # Creates a new image asset for marketing image and square marketing image + # if there are no assets' resource names specified. + marketing_image_asset_resource_name ||= create_image_asset( + client, + customer_id, + marketing_image_url, + 'Marketing Image', + ) + square_marketing_image_asset_resource_name ||= create_image_asset( + client, + customer_id, + square_marketing_image_url, + 'Square Marketing Image', + ) + + # Creates an ad group ad operation. + operation = client.operation.create_resource.ad_group_ad do |aga| + aga.ad_group = ad_group + aga.status = :PAUSED + aga.ad = client.resource.ad do |a| + a.final_urls << "https://www.example.com" + a.responsive_display_ad = client.resource.responsive_display_ad_info do |rda| + # Sets some basic required information for the responsive display ad. + rda.headlines << client.resource.ad_text_asset do |ata| + ata.text = 'Travel' + end + rda.long_headline = client.resource.ad_text_asset do |ata| + ata.text = 'Travel the World' + end + rda.descriptions << client.resource.ad_text_asset do |ata| + ata.text = 'Take to the air!' + end + rda.business_name = 'Google' + # Sets the marketing image and square marketing image to the previously + # created image assets. + rda.marketing_images << client.resource.ad_image_asset do |aia| + aia.asset = marketing_image_asset_resource_name + end + rda.square_marketing_images << client.resource.ad_image_asset do |aia| + aia.asset = square_marketing_image_asset_resource_name + end + # Optional: Sets call to action text, price prefix and promotion text. + rda.call_to_action_text = 'Shop Now' + rda.price_prefix = 'as low as' + rda.promo_text = 'Free shipping!' + end + end + end + + # Issues a mutate request to add the ad group ad. + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation], + ) + + # Prints out some information about the newly created ad. + resource_name = response.results.first.resource_name + puts "Added ad group ad named #{resource_name}" + + resource_name +end + +def create_image_asset(client, customer_id, image_url, image_name) + # Creates an asset operation. + operation = client.operation.create_resource.asset do |a| + a.name = image_name + a.type = :IMAGE + a.image_asset = client.resource.image_asset do |image| + image.data = open(image_url) { |f| f.read } + end + end + + # Issues a mutate request to add the asset. + response = client.service.asset.mutate_assets( + customer_id: customer_id, + operations: [operation], + ) + + # Prints out information about the newly added asset. + resource_name = response.results.first.resource_name + puts "A new image asset has been added with resource name: #{resource_name}" + + resource_name +end + +if __FILE__ == $0 + options = {} + + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-m', + '--marketing_image_asset_resource_name MARKETING_IMAGE_ASSET_RESOURCE_NAME', + String, + 'Marketing image asset resource name', + ) do |v| + options[:marketing_image_asset_resource_name] = v + end + + opts.on('-s', + '--square_marketing_image_asset_resource_name SQUARE_MARKETING_IMAGE_ASSET_RESOURCE_NAME', + String, + 'Square marketing image asset resource name', + ) do |v| + options[:square_marketing_image_asset_resource_name] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + add_smart_display_ad( + options.fetch(:customer_id).tr("-", ""), + options[:marketing_image_asset_resource_name], + options[:square_marketing_image_asset_resource_name]) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/advanced_operations/create_and_attach_shared_keyword_set.rb b/examples/advanced_operations/create_and_attach_shared_keyword_set.rb index 7bb8885f8..a4d5009a6 100755 --- a/examples/advanced_operations/create_and_attach_shared_keyword_set.rb +++ b/examples/advanced_operations/create_and_attach_shared_keyword_set.rb @@ -39,7 +39,10 @@ def create_and_attach_shared_keyword_set(customer_id, campaign_id) operation = client.operation.create_resource.shared_set(shared_set) - response = client.service.shared_set.mutate_shared_sets(customer_id, [operation]) + response = client.service.shared_set.mutate_shared_sets( + customer_id: customer_id, + operations: [operation], + ) shared_set_resource_name = response.results.first.resource_name puts "Created shared set #{shared_set_resource_name}" @@ -58,7 +61,10 @@ def create_and_attach_shared_keyword_set(customer_id, campaign_id) client.operation.create_resource.shared_criterion(criterion) end - response = client.service.shared_criterion.mutate_shared_criteria(customer_id, operations) + response = client.service.shared_criterion.mutate_shared_criteria( + customer_id: customer_id, + operations: operations, + ) response.results.each do |result| puts "Created shared criterion #{result.resource_name}" @@ -72,7 +78,9 @@ def create_and_attach_shared_keyword_set(customer_id, campaign_id) operation = client.operation.create_resource.campaign_shared_set(campaign_set) response = client.service.campaign_shared_set.mutate_campaign_shared_sets( - customer_id, [operation]) + customer_id: customer_id, + operations: [operation], + ) puts "Created campaign shared set #{response.results.first.resource_name}" end @@ -130,10 +138,5 @@ def create_and_attach_shared_keyword_set(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/find_and_remove_criteria_from_shared_set.rb b/examples/advanced_operations/find_and_remove_criteria_from_shared_set.rb index 3474a947f..229345d35 100755 --- a/examples/advanced_operations/find_and_remove_criteria_from_shared_set.rb +++ b/examples/advanced_operations/find_and_remove_criteria_from_shared_set.rb @@ -38,7 +38,11 @@ def find_and_remove_criteria_from_shared_set(customer_id, campaign_id) WHERE campaign.id = #{campaign_id} QUERY - response = ga_service.search(customer_id, query, page_size: PAGE_SIZE) + response = ga_service.search( + customer_id: customer_id, + query: query, + page_size: PAGE_SIZE, + ) response.each do |row| shared_set = row.shared_set @@ -55,7 +59,11 @@ def find_and_remove_criteria_from_shared_set(customer_id, campaign_id) WHERE shared_set.id IN (#{shared_set_ids.join(',')}) QUERY - response = ga_service.search(customer_id, query, page_size: PAGE_SIZE) + response = ga_service.search( + customer_id: customer_id, + query: query, + page_size: PAGE_SIZE, + ) response.each do |row| sc = row.shared_criterion @@ -74,7 +82,10 @@ def find_and_remove_criteria_from_shared_set(customer_id, campaign_id) client.operation.remove_resource.shared_criterion(criterion) end - response = client.service.shared_criterion.mutate_shared_criteria(customer_id, operations) + response = client.service.shared_criterion.mutate_shared_criteria( + customer_id: customer_id, + operations: operations, + ) response.results.each do |result| puts "Removed shared criterion #{result.resource_name}" end @@ -135,10 +146,5 @@ def find_and_remove_criteria_from_shared_set(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/get_ad_group_bid_modifiers.rb b/examples/advanced_operations/get_ad_group_bid_modifiers.rb index 943fd68b7..b86499e36 100644 --- a/examples/advanced_operations/get_ad_group_bid_modifiers.rb +++ b/examples/advanced_operations/get_ad_group_bid_modifiers.rb @@ -39,9 +39,9 @@ def get_ad_group_bid_modifiers(customer_id, ad_group_id = nil) end response = client.service.google_ads.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) response.each do |row| @@ -119,10 +119,5 @@ def get_ad_group_bid_modifiers(customer_id, ad_group_id = nil) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/advanced_operations/use_portfolio_bidding_strategy.rb b/examples/advanced_operations/use_portfolio_bidding_strategy.rb index 17dab8371..0562055ef 100755 --- a/examples/advanced_operations/use_portfolio_bidding_strategy.rb +++ b/examples/advanced_operations/use_portfolio_bidding_strategy.rb @@ -38,7 +38,9 @@ def use_portfolio_bidding_strategy(customer_id) operation = client.operation.create_resource.campaign_budget(budget) response = client.service.campaign_budget.mutate_campaign_budgets( - customer_id, [operation]) + customer_id: customer_id, + operations: [operation], + ) budget_id = response.results.first.resource_name puts "Budget #{budget_id} was created" @@ -52,7 +54,9 @@ def use_portfolio_bidding_strategy(customer_id) operation = client.operation.create_resource.bidding_strategy(bidding_strategy) response = client.service.bidding_strategy.mutate_bidding_strategies( - customer_id, [operation]) + customer_id: customer_id, + operations: [operation], + ) bidding_id = response.results.first.resource_name puts "Portfolio bidding strategy #{bidding_id} was created" @@ -78,7 +82,9 @@ def use_portfolio_bidding_strategy(customer_id) client.operation.create_resource.campaign(c) } response = client.service.campaign.mutate_campaigns( - customer_id, campaign_operations) + customer_id: customer_id, + operations: campaign_operations, + ) response.results.each do |c| puts "Campaign #{c.resource_name} was created" end @@ -131,10 +137,5 @@ def use_portfolio_bidding_strategy(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/add_ad_groups.rb b/examples/basic_operations/add_ad_groups.rb index a6a751923..494ce3ee4 100755 --- a/examples/basic_operations/add_ad_groups.rb +++ b/examples/basic_operations/add_ad_groups.rb @@ -40,7 +40,9 @@ def add_ad_groups(customer_id, campaign_id) # Add the ad group. response = client.service.ad_group.mutate_ad_groups( - customer_id, [ad_group_operation]) + customer_id: customer_id, + operations: [ad_group_operation], + ) puts "Created ad group #{response.results.first.resource_name}." end @@ -97,11 +99,5 @@ def add_ad_groups(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end - diff --git a/examples/basic_operations/add_campaigns.rb b/examples/basic_operations/add_campaigns.rb index a6b10481f..e857fca4e 100755 --- a/examples/basic_operations/add_campaigns.rb +++ b/examples/basic_operations/add_campaigns.rb @@ -37,7 +37,9 @@ def add_campaigns(customer_id) # Add budget. return_budget = client.service.campaign_budget.mutate_campaign_budgets( - customer_id, [operation]) + customer_id: customer_id, + operations: [operation], + ) # Create campaign. campaign = client.resource.campaign do |c| @@ -73,7 +75,9 @@ def add_campaigns(customer_id) # Add the campaign. response = client.service.campaign.mutate_campaigns( - customer_id, [campaign_operation]) + customer_id: customer_id, + operations: [campaign_operation], + ) puts "Created campaign #{response.results.first.resource_name}." end @@ -125,10 +129,5 @@ def add_campaigns(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/add_expanded_text_ads.rb b/examples/basic_operations/add_expanded_text_ads.rb index 6ee9956a6..456c23640 100755 --- a/examples/basic_operations/add_expanded_text_ads.rb +++ b/examples/basic_operations/add_expanded_text_ads.rb @@ -50,7 +50,9 @@ def add_expanded_text_ads(customer_id, ad_group_id) # Add the ad group ad. response = client.service.ad_group_ad.mutate_ad_group_ads( - customer_id, [ad_group_ad_operation]) + customer_id: customer_id, + operations: [ad_group_ad_operation], + ) puts "Created expanded text ad #{response.results.first.resource_name}." end @@ -107,11 +109,5 @@ def add_expanded_text_ads(customer_id, ad_group_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end - diff --git a/examples/basic_operations/add_keywords.rb b/examples/basic_operations/add_keywords.rb index 059c8b13a..f2c046c04 100755 --- a/examples/basic_operations/add_keywords.rb +++ b/examples/basic_operations/add_keywords.rb @@ -46,7 +46,10 @@ def add_keywords(customer_id, ad_group_id, keyword) # Add keyword operation = client.operation.create_resource.ad_group_criterion(criterion) - response = client.service.ad_group_criterion.mutate_ad_group_criteria(customer_id, [operation]) + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: [operation], + ) puts "Created keyword #{response.results.first.resource_name}" end @@ -108,10 +111,5 @@ def add_keywords(customer_id, ad_group_id, keyword) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/add_responsive_search_ad.rb b/examples/basic_operations/add_responsive_search_ad.rb index 6d2b9a032..1a52d6baf 100644 --- a/examples/basic_operations/add_responsive_search_ad.rb +++ b/examples/basic_operations/add_responsive_search_ad.rb @@ -61,7 +61,10 @@ def add_responsive_search_ad(customer_id, ad_group_id) op = client.operation.create_resource.ad_group_ad(ad_group_ad) - response = client.service.ad_group_ad.mutate_ad_group_ads(customer_id, [op]) + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [op], + ) puts "Created Responsive Search Ad with ID #{response.results.first.resource_name}." end @@ -120,10 +123,5 @@ def add_responsive_search_ad(customer_id, ad_group_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/get_ad_groups.rb b/examples/basic_operations/get_ad_groups.rb index d85e38b9f..92e3e1da4 100755 --- a/examples/basic_operations/get_ad_groups.rb +++ b/examples/basic_operations/get_ad_groups.rb @@ -31,7 +31,11 @@ def get_ad_groups(customer_id, campaign_id = nil) search_query << " WHERE campaign.id = #{campaign_id}" end - response = client.service.google_ads.search(customer_id, search_query, page_size: PAGE_SIZE) + response = client.service.google_ads.search( + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, + ) response.each do |row| puts "AdGroup with ID #{row.ad_group.id} and name '#{row.ad_group.name}' was found in " \ @@ -94,10 +98,5 @@ def get_ad_groups(customer_id, campaign_id = nil) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/get_artifact_metadata.rb b/examples/basic_operations/get_artifact_metadata.rb index 6aed9f518..472a88f1b 100644 --- a/examples/basic_operations/get_artifact_metadata.rb +++ b/examples/basic_operations/get_artifact_metadata.rb @@ -42,7 +42,7 @@ def get_artifact_metadata(artifact_name) WHERE name = '#{artifact_name}' QUERY - response = client.service.google_ads_field.search_google_ads_fields(query) + response = client.service.google_ads_field.search_google_ads_fields(query: query) if response.response.results.empty? puts "The specified artifact '#{artifact_name}' doesn't exist" @@ -51,13 +51,13 @@ def get_artifact_metadata(artifact_name) response.each do |row| puts "An artifact named '#{row.name}' with category '#{row.category}' and data type " \ - "#{row.data_type} #{is_or_not(row.selectable.value)} selectable, " \ - "#{is_or_not(row.filterable.value)} filterable, #{is_or_not(row.sortable.value)} " \ - "sortable, and #{is_or_not(row.is_repeated.value)} repeated." + "#{row.data_type} #{is_or_not(row.selectable)} selectable, " \ + "#{is_or_not(row.filterable)} filterable, #{is_or_not(row.sortable)} " \ + "sortable, and #{is_or_not(row.is_repeated)} repeated." if !row.selectable_with.empty? puts "The artifact can be selected with the following artifacts:" - puts (row.selectable_with.sort_by { |field| field.value }) + puts (row.selectable_with.sort_by { |field| field }) end end end @@ -113,10 +113,5 @@ def is_or_not(bool) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/get_campaigns.rb b/examples/basic_operations/get_campaigns.rb index 71b6fb87d..f8bde530c 100755 --- a/examples/basic_operations/get_campaigns.rb +++ b/examples/basic_operations/get_campaigns.rb @@ -26,8 +26,9 @@ def get_campaigns(customer_id) client = Google::Ads::GoogleAds::GoogleAdsClient.new responses = client.service.google_ads.search_stream( - customer_id, - 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id') + customer_id: customer_id, + query: 'SELECT campaign.id, campaign.name FROM campaign ORDER BY campaign.id', + ) responses.each do |response| response.results.each do |row| @@ -85,10 +86,5 @@ def get_campaigns(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/get_expanded_text_ads.rb b/examples/basic_operations/get_expanded_text_ads.rb index 1b5f5ef01..36d51fd62 100755 --- a/examples/basic_operations/get_expanded_text_ads.rb +++ b/examples/basic_operations/get_expanded_text_ads.rb @@ -42,9 +42,9 @@ def get_expanded_text_ads(customer_id, ad_group_id = nil) end response = client.service.google_ads.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) response.each do |row| @@ -114,10 +114,5 @@ def get_expanded_text_ads(customer_id, ad_group_id = nil) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/get_keywords.rb b/examples/basic_operations/get_keywords.rb index 94f103685..05ad8562d 100755 --- a/examples/basic_operations/get_keywords.rb +++ b/examples/basic_operations/get_keywords.rb @@ -40,9 +40,9 @@ def get_keywords(customer_id, ad_group_id=nil) end response = client.service.google_ads.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) response.each do |row| @@ -110,10 +110,5 @@ def get_keywords(customer_id, ad_group_id=nil) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/get_responsive_search_ads.rb b/examples/basic_operations/get_responsive_search_ads.rb new file mode 100755 index 000000000..aaedb5863 --- /dev/null +++ b/examples/basic_operations/get_responsive_search_ads.rb @@ -0,0 +1,126 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# This code example gets non-removed responsive search ads in +# a specified ad group. +# To add responsive search ads, run add_responsive_search_ad.rb. +# To get ad groups, run get_ad_groups.rb. + +require 'optparse' +require 'google/ads/google_ads' + +def get_responsive_search_ads(customer_id, ad_group_id = nil) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + search_query = <<~QUERY + SELECT ad_group.id, + ad_group_ad.ad.id, + ad_group_ad.ad.responsive_search_ad.headlines, + ad_group_ad.ad.responsive_search_ad.descriptions, + ad_group_ad.status + FROM ad_group_ad + WHERE ad_group_ad.ad.type = RESPONSIVE_SEARCH_AD + AND ad_group_ad.status != 'REMOVED' + QUERY + + if ad_group_id + search_query << " AND ad_group.id = #{ad_group_id}" + end + + responses = client.service.google_ads.search_stream( + customer_id: customer_id, + query: search_query + ) + + responses.each do |response| + response.results.each do |row| + ad = row.ad_group_ad.ad + if ad.responsive_search_ad + rsa = ad.responsive_search_ad + + headlines = '' + rsa.headlines.each{ |h| headlines << h.text << ', ' } + + descriptions = '' + rsa.descriptions.each{ |d| descriptions << d.text << ', ' } + + puts "Responsive search ad with ID #{ad.id}, status #{row.ad_group_ad.status}, " \ + "headlines '#{headlines}', and descriptions '#{descriptions}' was found in ad group " \ + "with ID #{row.ad_group.id}." + end + end + end +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:ad_group_id] = nil + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-A', '--ad-group-id AD-GROUP-ID', String, + '(Optional) Ad Group ID') do |v| + options[:ad_group_id] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + get_responsive_search_ads(options.fetch(:customer_id).tr("-", ""), options[:ad_group_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("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/basic_operations/pause_ad.rb b/examples/basic_operations/pause_ad.rb index db21efc45..e1291d663 100755 --- a/examples/basic_operations/pause_ad.rb +++ b/examples/basic_operations/pause_ad.rb @@ -31,7 +31,10 @@ def pause_ad(customer_id, ad_group_id, ad_id) aga.status = :PAUSED end - response = client.service.ad_group_ad.mutate_ad_group_ads(customer_id, [operation]) + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation], + ) puts "Paused ad #{response.results.first.resource_name}" end @@ -93,10 +96,5 @@ def pause_ad(customer_id, ad_group_id, ad_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/remove_ad.rb b/examples/basic_operations/remove_ad.rb index e2e46cfa7..55d862f2c 100755 --- a/examples/basic_operations/remove_ad.rb +++ b/examples/basic_operations/remove_ad.rb @@ -29,7 +29,10 @@ def remove_ad(customer_id, ad_group_id, ad_id) operation = client.operation.remove_resource.ad_group_ad(resource) - response = client.service.ad_group_ad.mutate_ad_group_ads(customer_id, [operation]) + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation], + ) puts "Removed ad #{response.results.first.resource_name}" end @@ -91,10 +94,5 @@ def remove_ad(customer_id, ad_group_id, ad_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/remove_ad_group.rb b/examples/basic_operations/remove_ad_group.rb index cbf66a8ab..00de70342 100755 --- a/examples/basic_operations/remove_ad_group.rb +++ b/examples/basic_operations/remove_ad_group.rb @@ -29,7 +29,10 @@ def remove_ad_group(customer_id, ad_group_id) operation = client.operation.remove_resource.ad_group(resource) - response = client.service.ad_group.mutate_ad_groups(customer_id, [operation]) + response = client.service.ad_group.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) puts "Removed ad group #{response.results.first.resource_name}" end @@ -88,10 +91,5 @@ def remove_ad_group(customer_id, ad_group_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/remove_campaign.rb b/examples/basic_operations/remove_campaign.rb index 650502abb..7070b5433 100755 --- a/examples/basic_operations/remove_campaign.rb +++ b/examples/basic_operations/remove_campaign.rb @@ -29,7 +29,10 @@ def remove_campaign(customer_id, campaign_id) operation = client.operation.remove_resource.campaign(resource) - response = client.service.campaign.mutate_campaigns(customer_id, [operation]) + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) puts "Campaign with resource name = '#{response.results.first.resource_name}' was removed." end @@ -86,10 +89,5 @@ def remove_campaign(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/remove_keyword.rb b/examples/basic_operations/remove_keyword.rb index 7304448d1..422bbc634 100755 --- a/examples/basic_operations/remove_keyword.rb +++ b/examples/basic_operations/remove_keyword.rb @@ -29,7 +29,10 @@ def remove_keyword(customer_id, ad_group_id, criteria_id) # Remove keyword operation = client.operation.remove_resource.ad_group_criterion(resource) - response = client.service.ad_group_criterion.mutate_ad_group_criteria(customer_id, [operation]) + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: [operation], + ) puts "Removed keyword #{response.results.first.resource_name}" end @@ -44,7 +47,7 @@ def remove_keyword(customer_id, ad_group_id, criteria_id) # code. # # Running the example with -h will print the command line usage. - options[:customer_id] = 'INSERT_ADWORDS_CUSTOMER_ID_HERE' + options[:customer_id] = 'INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE' options[:ad_group_id] = 'INSERT_AD_GROUP_ID_HERE' options[:criteria_id] = 'INSERT_CRITERIA_ID_HERE' @@ -79,24 +82,19 @@ def remove_keyword(customer_id, ad_group_id, criteria_id) begin remove_keyword( options.fetch(:customer_id).tr("-", ""), options[:ad_group_id], options[:criteria_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("\tOn field: %s\n", field_path_element.field_name) - end - end - error.error_code.to_h.each do |k, v| - next if v == :UNSPECIFIED - STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) end end - raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end raise end end diff --git a/examples/basic_operations/update_ad_group.rb b/examples/basic_operations/update_ad_group.rb index 7f920a9f1..d2468d350 100755 --- a/examples/basic_operations/update_ad_group.rb +++ b/examples/basic_operations/update_ad_group.rb @@ -32,7 +32,10 @@ def update_ad_group(customer_id, ad_group_id, bid_micro_amount) ag.cpc_bid_micros = bid_micro_amount end - response = client.service.ad_group.mutate_ad_groups(customer_id, [operation]) + response = client.service.ad_group.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) puts "Ad group with resource name = '#{response.results.first.resource_name}' was updated." end @@ -65,7 +68,7 @@ def update_ad_group(customer_id, ad_group_id, bid_micro_amount) options[:ad_group_id] = v end - opts.on('-b', '--cpd-bid-micro-amount CPC-BID-MICRO-AMOUNT', Integer, + opts.on('-b', '--cpc-bid-micro-amount CPC-BID-MICRO-AMOUNT', Integer, 'Bid Micro Amount') do |v| options[:bid_micro_amount] = v end @@ -82,24 +85,19 @@ def update_ad_group(customer_id, ad_group_id, bid_micro_amount) begin update_ad_group(options.fetch(:customer_id).tr("-", ""), options[:ad_group_id], options[:bid_micro_amount]) - rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e - e.failure.errors.each do |error| - STDERR.printf("Error with message: %s\n", error.message) - if error.location - error.location.field_path_elements.each do |field_path_element| - STDERR.printf("\tOn field: %s\n", field_path_element.field_name) - end - end - error.error_code.to_h.each do |k, v| - next if v == :UNSPECIFIED - STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) end end - raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end raise end end diff --git a/examples/basic_operations/update_campaign.rb b/examples/basic_operations/update_campaign.rb index a33ccae28..be3a14ec8 100755 --- a/examples/basic_operations/update_campaign.rb +++ b/examples/basic_operations/update_campaign.rb @@ -35,7 +35,10 @@ def update_campaign(customer_id, campaign_id) end end - response = client.service.campaign.mutate_campaigns(customer_id, [operation]) + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) puts "Campaign with resource name = '#{response.results.first.resource_name}' was updated." end @@ -92,10 +95,5 @@ def update_campaign(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/basic_operations/update_expanded_text_ad.rb b/examples/basic_operations/update_expanded_text_ad.rb new file mode 100755 index 000000000..02e163f35 --- /dev/null +++ b/examples/basic_operations/update_expanded_text_ad.rb @@ -0,0 +1,105 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example updates an expanded text ad. +# To get expanded text ads, run get_expanded_text_ads.rb. + +require 'optparse' +require 'google/ads/google_ads' +require 'date' + +def update_expanded_text_ad(customer_id, ad_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + ad_resource_name = client.path.ad(customer_id, ad_id) + + # Create the operation for updating the ad. + ad_operation = client.operation.update_resource.ad(ad_resource_name) do |ad| + ad.final_urls << 'http://www.example.com' + ad.final_mobile_urls << 'http://www.example.com/mobile' + ad.expanded_text_ad = client.resource.expanded_text_ad_info do |eta| + eta.headline_part1 = "Cruise to Pluto #{(Time.new.to_f * 100).to_i}" + eta.headline_part2 = 'Tickets on sales now' + end + end + + # Update the ad. + response = client.service.ad.mutate_ads( + customer_id: customer_id, + operations: [ad_operation], + ) + + puts "Updated expanded text ad #{response.results.first.resource_name}." +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:ad_id] = 'INSERT_AD_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-a', '--ad-id AD-ID', String, 'Ad ID') do |v| + options[:ad_id] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + update_expanded_text_ad(options.fetch(:customer_id).tr('-', ''), options[:ad_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('\tOn field: %s\n', field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf('\tType: %s\n\tCode: %s\n', k, v) + end + end + raise + end +end + diff --git a/examples/basic_operations/update_keyword.rb b/examples/basic_operations/update_keyword.rb index fccb37c4e..c446f7427 100755 --- a/examples/basic_operations/update_keyword.rb +++ b/examples/basic_operations/update_keyword.rb @@ -40,7 +40,10 @@ def update_keyword(customer_id, ad_group_id, criteria_id) end # Update keyword - response = client.service.ad_group_criterion.mutate_ad_group_criteria(customer_id, [operation]) + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: [operation], + ) puts "Updated keyword #{response.results.first.resource_name}" end @@ -55,7 +58,7 @@ def update_keyword(customer_id, ad_group_id, criteria_id) # code. # # Running the example with -h will print the command line usage. - options[:customer_id] = "INSERT_ADWORDS_CUSTOMER_ID_HERE" + options[:customer_id] = "INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE" options[:ad_group_id] = "INSERT_AD_GROUP_ID_HERE" options[:criteria_id] = "INSERT_CRITERIA_ID_HERE" @@ -90,24 +93,19 @@ def update_keyword(customer_id, ad_group_id, criteria_id) begin update_keyword(options.fetch(:customer_id).tr("-", ""), options[:ad_group_id], options[:criteria_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("\tOn field: %s\n", field_path_element.field_name) - end - end - error.error_code.to_h.each do |k, v| - next if v == :UNSPECIFIED - STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) end end - raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end raise end end diff --git a/examples/billing/add_account_budget_proposal.rb b/examples/billing/add_account_budget_proposal.rb index 697a541ff..af76117aa 100644 --- a/examples/billing/add_account_budget_proposal.rb +++ b/examples/billing/add_account_budget_proposal.rb @@ -59,8 +59,8 @@ def add_account_budget_proposal(customer_id, billing_setup_id) account_budget_proposal_service = client.service.account_budget_proposal # Add budget proposal. response = account_budget_proposal_service.mutate_account_budget_proposal( - customer_id, - operation, + customer_id: customer_id, + operation: operation, ) puts sprintf("Created budget proposal %s.", @@ -121,10 +121,5 @@ def add_account_budget_proposal(customer_id, billing_setup_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/billing/get_account_budget_proposals.rb b/examples/billing/get_account_budget_proposals.rb index 3d84267a7..4d475c057 100644 --- a/examples/billing/get_account_budget_proposals.rb +++ b/examples/billing/get_account_budget_proposals.rb @@ -45,9 +45,9 @@ def get_account_budget_proposals(customer_id) QUERY response = ga_service.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) response.each do |row| @@ -121,10 +121,5 @@ def get_account_budget_proposals(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/billing/get_account_budgets.rb b/examples/billing/get_account_budgets.rb index 53439b56d..5bc9f0303 100644 --- a/examples/billing/get_account_budgets.rb +++ b/examples/billing/get_account_budgets.rb @@ -46,9 +46,9 @@ def get_account_budgets(customer_id) QUERY response = ga_service.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) # Iterates over all rows in all pages and prints the requested field values @@ -159,10 +159,5 @@ def get_account_budgets(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/billing/get_billing_setup.rb b/examples/billing/get_billing_setup.rb index 0259b9955..10f0aee06 100644 --- a/examples/billing/get_billing_setup.rb +++ b/examples/billing/get_billing_setup.rb @@ -42,9 +42,9 @@ def get_billing_setup(customer_id) QUERY response = ga_service.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) response.each do |row| @@ -58,11 +58,11 @@ def get_billing_setup(customer_id) billing_setup.id, billing_setup.status, billing_setup.payments_account, - payments_account_info.payments_account_id, - payments_account_info.payments_account_name, - payments_account_info.payments_profile_id, - payments_account_info.payments_profile_name, - payments_account_info.secondary_payments_profile_id + payments_account_info ? payments_account_info.payments_account_id : "N/A", + payments_account_info ? payments_account_info.payments_account_name : "N/A", + payments_account_info ? payments_account_info.payments_profile_id : "N/A", + payments_account_info ? payments_account_info.payments_profile_name : "N/A", + payments_account_info ? payments_account_info.secondary_payments_profile_id : "N/A" ) end end @@ -116,10 +116,5 @@ def get_billing_setup(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/billing/remove_billing_setup.rb b/examples/billing/remove_billing_setup.rb index d40e36f18..6e4871b0d 100644 --- a/examples/billing/remove_billing_setup.rb +++ b/examples/billing/remove_billing_setup.rb @@ -29,11 +29,11 @@ def remove_billing_setup(customer_id, billing_setup_id) billing_setup_service = client.service.billing_setup resource = client.path.billing_setup(customer_id, billing_setup_id) - op = client.operation.remove_resource.billing_setup(resource) + operation = client.operation.remove_resource.billing_setup(resource) response = billing_setup_service.mutate_billing_setup( - customer_id, - op, + customer_id: customer_id, + operation: operation, ) puts sprintf("Removed billing_setup %s", response.results.first.resource_name) @@ -95,10 +95,5 @@ def remove_billing_setup(customer_id, billing_setup_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/add_campaign_bid_modifier.rb b/examples/campaign_management/add_campaign_bid_modifier.rb index 91a46bd57..142b67d61 100644 --- a/examples/campaign_management/add_campaign_bid_modifier.rb +++ b/examples/campaign_management/add_campaign_bid_modifier.rb @@ -42,8 +42,8 @@ def add_campaign_bid_modifier(customer_id, campaign_id, bid_modifier) campaign_bid_modifier_service = client.service.campaign_bid_modifier # Add the Campaign Bid Modifier response = campaign_bid_modifier_service.mutate_campaign_bid_modifiers( - customer_id, - [operation] + customer_id: customer_id, + operations: [operation], ) puts sprintf('Added %d campaign bid modifiers:', response.results.size) @@ -76,7 +76,7 @@ def add_campaign_bid_modifier(customer_id, campaign_id, bid_modifier) options[:customer_id] = v end - opts.on('-A', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| + opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| options[:campaign_id] = v end @@ -111,10 +111,5 @@ def add_campaign_bid_modifier(customer_id, campaign_id, bid_modifier) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/add_campaign_draft.rb b/examples/campaign_management/add_campaign_draft.rb index 32da270ee..b1c2bec3f 100644 --- a/examples/campaign_management/add_campaign_draft.rb +++ b/examples/campaign_management/add_campaign_draft.rb @@ -33,8 +33,8 @@ def add_campaign_draft(customer_id, campaign_id) end draft_response = client.service.campaign_draft.mutate_campaign_drafts( - customer_id, - [draft_operation], + customer_id: customer_id, + operations: [draft_operation], ) draft_resource_name = draft_response.results.first.resource_name @@ -48,19 +48,19 @@ def add_campaign_draft(customer_id, campaign_id) FROM campaign_draft WHERE campaign_draft.resource_name = "#{draft_resource_name}" EOQUERY - search_response = client.service.google_ads.search(customer_id, query) + search_response = client.service.google_ads.search(customer_id: customer_id, query: query) draft_campaign_resource_name = search_response.first.campaign_draft.draft_campaign criterion_operation = client.operation.create_resource.campaign_criterion do |cc| - cc.campaign = draft_campaign_resource_name + cc.campaign = draft_campaign_resource_name.value cc.language = client.resource.language_info do |li| li.language_constant = client.path.language_constant(1003) # Spanish end end criterion_response = client.service.campaign_criterion.mutate_campaign_criteria( - customer_id, - [criterion_operation] + customer_id: customer_id, + operations: [criterion_operation], ) puts "Campaign Criterion #{criterion_response.results.first.resource_name}" \ @@ -122,10 +122,5 @@ def add_campaign_draft(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/add_campaign_labels.rb b/examples/campaign_management/add_campaign_labels.rb index ec0bb3283..9bba50462 100644 --- a/examples/campaign_management/add_campaign_labels.rb +++ b/examples/campaign_management/add_campaign_labels.rb @@ -39,8 +39,10 @@ def add_campaign_label(customer_id, label_id, campaign_ids) client.operation.create_resource.campaign_label(label) } - campaign_label_service = client.service.campaign_label - response = campaign_label_service.mutate_campaign_labels(customer_id, ops) + response = client.service.campaign_label.mutate_campaign_labels( + customer_id: customer_id, + operations: ops, + ) response.results.each do |result| puts("Created campaign label with id: #{result.resource_name}") end @@ -107,10 +109,5 @@ def add_campaign_label(customer_id, label_id, campaign_ids) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/create_campaign_experiment.rb b/examples/campaign_management/create_campaign_experiment.rb index 5b8daf1e0..3992a41f9 100644 --- a/examples/campaign_management/create_campaign_experiment.rb +++ b/examples/campaign_management/create_campaign_experiment.rb @@ -34,12 +34,11 @@ def create_campaign_experiment(customer_id, campaign_draft_resource_name) ce.traffic_split_type = :RANDOM_QUERY end - # A Google::Longrunning::Operation (LRO) is returned from this asynchronous - # request by the API, and is converted into a Google::Gax::Operation - # automatically. + # A Gapic::Operation is returned from this asynchronous request by the API, + # and is converted into a Google::Gax::Operation automatically. operation = client.service.campaign_experiment.create_campaign_experiment( - customer_id, - experiment + customer_id: customer_id, + campaign_experiment: experiment, ) puts "Asynchronous request to create campaign experiment with " + @@ -62,7 +61,7 @@ def create_campaign_experiment(customer_id, campaign_draft_resource_name) WHERE campaign_experiment.resource_name = "#{operation.metadata.campaign_experiment}" EOQUERY - response = client.service.google_ads.search(customer_id, query) + response = client.service.google_ads.search(customer_id: customer_id, query: query) puts "Experiment campaign #{response.first.campaign_experiment.experiment_campaign}" \ " finished creating." end @@ -125,11 +124,5 @@ def create_campaign_experiment(customer_id, campaign_draft_resource_name) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end - diff --git a/examples/campaign_management/get_all_disapproved_ads.rb b/examples/campaign_management/get_all_disapproved_ads.rb index 7927fef13..104d0ff86 100644 --- a/examples/campaign_management/get_all_disapproved_ads.rb +++ b/examples/campaign_management/get_all_disapproved_ads.rb @@ -31,16 +31,18 @@ def get_all_disapproved_ads(customer_id, campaign_id) SELECT ad_group_ad.ad.id, ad_group_ad.ad.type, - ad_group_ad.policy_summary + ad_group_ad.policy_summary.approval_status, + ad_group_ad.policy_summary.policy_topic_entries FROM ad_group_ad WHERE campaign.id = #{campaign_id} + AND ad_group_ad.policy_summary.approval_status = DISAPPROVED QUERY # Issue the search request. response = ga_service.search( - customer_id, - search_query, - page_size: PAGE_SIZE + customer_id: customer_id, + query: search_query, + page_size: PAGE_SIZE, ) disapproved_ads_count = 0; @@ -49,11 +51,6 @@ def get_all_disapproved_ads(customer_id, campaign_id) response.each do |row| ad_group_ad = row.ad_group_ad ad = ad_group_ad.ad - policy_summary = ad_group_ad.policy_summary - - if policy_summary.approval_status.to_s != 'DISAPPROVED' - next - end disapproved_ads_count += 1 @@ -107,7 +104,7 @@ def get_all_disapproved_ads(customer_id, campaign_id) end opts.on('-c', '--campaign-id CAMPAIGN-ID', String, - '(Optional) Campaign ID') do |v| + 'Campaign ID') do |v| options[:campaign_id] = v end @@ -136,10 +133,5 @@ def get_all_disapproved_ads(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/get_campaigns_by_label.rb b/examples/campaign_management/get_campaigns_by_label.rb index ba153bd33..ab702df9c 100644 --- a/examples/campaign_management/get_campaigns_by_label.rb +++ b/examples/campaign_management/get_campaigns_by_label.rb @@ -32,9 +32,10 @@ def get_campaigns_by_label(customer_id, label_id) ga_service = client.service.google_ads response = ga_service.search( - customer_id, - query, - page_size: PAGE_SIZE) + customer_id: customer_id, + query: query, + page_size: PAGE_SIZE, + ) response.each do |row| puts "Campaign with ID #{row.campaign.id} and name '#{row.campaign.name}' was found." @@ -98,10 +99,5 @@ def get_campaigns_by_label(customer_id, label_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/graduate_campaign_experiment.rb b/examples/campaign_management/graduate_campaign_experiment.rb index 7b987ba7c..66dcee40b 100644 --- a/examples/campaign_management/graduate_campaign_experiment.rb +++ b/examples/campaign_management/graduate_campaign_experiment.rb @@ -36,8 +36,8 @@ def graduate_campaign_experiment(customer_id, experiment_resource_name) end response = client.service.campaign_budget.mutate_campaign_budgets( - customer_id, - [budget_operation] + customer_id: customer_id, + operations: [budget_operation], ) budget_resource_name = response.results.first.resource_name @@ -45,8 +45,8 @@ def graduate_campaign_experiment(customer_id, experiment_resource_name) # Graduate the campaign using the budget created above. response = client.service.campaign_experiment.graduate_campaign_experiment( - experiment_resource_name, - budget_resource_name + campaign_experiment: experiment_resource_name, + campaign_budget: budget_resource_name, ) puts "Campaign #{response.graduated_campaign} is now graduated." @@ -108,10 +108,5 @@ def graduate_campaign_experiment(customer_id, experiment_resource_name) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/set_ad_parameters.rb b/examples/campaign_management/set_ad_parameters.rb index 3f705925d..efd611e11 100644 --- a/examples/campaign_management/set_ad_parameters.rb +++ b/examples/campaign_management/set_ad_parameters.rb @@ -52,7 +52,10 @@ def add_ad_parameters(customer_id, ad_group_id, criterion_id) operations = [op_1, op_2] ad_parameter_service = client.service.ad_parameter - response = ad_parameter_service.mutate_ad_parameters(customer_id, operations) + response = ad_parameter_service.mutate_ad_parameters( + customer_id: customer_id, + operations: operations, + ) response.results.each do |result| puts("Created ad parameter with id #{result.resource_name}") @@ -83,12 +86,11 @@ def add_ad_parameters(customer_id, ad_group_id, criterion_id) options[:customer_id] = v end - opts.on('-A', '--ad-group-id AD-GROUP-ID', String, - '(Optional) AdGroup ID') do |v| + opts.on('-A', '--ad-group-id AD-GROUP-ID', String, 'AdGroup ID') do |v| options[:ad_group_id] = v end - opts.on('-c', '--criterion-id CRITERION-ID', String, 'Criterion ID') do |v| + opts.on('-r', '--criterion-id CRITERION-ID', String, 'Criterion ID') do |v| options[:criterion_id] = v end @@ -121,10 +123,5 @@ def add_ad_parameters(customer_id, ad_group_id, criterion_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/campaign_management/update_campaign_criterion_bid_modifier.rb b/examples/campaign_management/update_campaign_criterion_bid_modifier.rb new file mode 100644 index 000000000..65542ab63 --- /dev/null +++ b/examples/campaign_management/update_campaign_criterion_bid_modifier.rb @@ -0,0 +1,130 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example demonstrates how to update a campaign criterion with a new bid +# modifier. + +require 'optparse' +require 'google/ads/google_ads' + +def update_campaign_criterion_bid_modifier( + customer_id, + campaign_id, + criterion_id, + bid_modifier + ) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # Create the resource name. + resource_name = client.path.campaign_criterion( + customer_id, + campaign_id, + criterion_id + ) + + operation = client.operation.update_resource.campaign_criterion(resource_name) do |campaign_criterion| + # Sets the Bid Modifier. + campaign_criterion.bid_modifier = bid_modifier + end + + # Create campaign criterion Service + criteria_service = client.service.campaign_criterion + + # Update the Campaign Bid Modifier + response = criteria_service.mutate_campaign_criteria( + customer_id: customer_id, + operations: [operation] + ) + + puts "Updated #{response.results.size} campaign criterion bid modifiers:" + response.results.each do |criteria_service| + puts "\t#{criteria_service.resource_name}" + end +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' + options[:criterion_id] = 'INSERT_CRITERION_ID_HERE' + options[:bid_modifier] = 'INSERT_BID_MODIFIER_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| + options[:campaign_id] = v + end + + opts.on('-r', '--criterion-id CRITERION-ID', String, 'Criterion ID') do |v| + options[:criterion_id] = v + end + + opts.on('-B', '--bid-modifier-value BID-MODIFIER-VALUE', String, + 'Bid Modifier') do |v| + options[:bid_modifier] = v.to_f + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + update_campaign_criterion_bid_modifier( + options.fetch(:customer_id).tr("-", ""), + options[:campaign_id], + options[:criterion_id], + options[:bid_modifier] + ) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/error_handling/handle_partial_failure.rb b/examples/error_handling/handle_partial_failure.rb index b90038170..ad9e0158a 100644 --- a/examples/error_handling/handle_partial_failure.rb +++ b/examples/error_handling/handle_partial_failure.rb @@ -47,8 +47,8 @@ def add_ad_groups(customer_id, campaign_id) end response = client.service.ad_group.mutate_ad_groups( - customer_id, - operations, + customer_id: customer_id, + operations: operations, partial_failure: true, ) @@ -135,10 +135,5 @@ def add_ad_groups(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/extensions/add_prices.rb b/examples/extensions/add_prices.rb new file mode 100755 index 000000000..cf7a46321 --- /dev/null +++ b/examples/extensions/add_prices.rb @@ -0,0 +1,155 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Adds a price extension and associates it with an account. +# Campaign targeting is also set using the specified campaign ID. To get +# campaigns, run basic_operations/get_campaigns.rb. + +require 'optparse' +require 'google/ads/google_ads' +require 'date' + +def add_prices(customer_id, campaign_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # The operation creates a customer extension setting with price feed item. + # This associates the price extension to your account. + operation = client.operation.create_resource.extension_feed_item do |efi| + efi.extension_type = :PRICE + efi.price_feed_item = client.resource.price_feed_item do |pfi| + pfi.type = :SERVICES + # Optional: set price qualifier. + pfi.price_qualifier = :FROM + pfi.tracking_url_template = 'http://tracker.example.com/?u={lpurl}' + pfi.language_code = 'en' + + # To create a price extension, at least three price offerings are needed. + pfi.price_offerings << create_price_offer( + client, 'Scrubs', 'Body Scrub, Salt Scrub', 60_000_000, # 60 USD + 'USD', :PER_HOUR, 'http://www.example.com/scrubs', + 'http://m.example.com/scrubs') + pfi.price_offerings << create_price_offer( + client, 'Hair Cuts', 'Once a month', 75_000_000, # 75 USD + 'USD', :PER_MONTH, 'http://www.example.com/haircuts', + 'http://m.example.com/haircuts') + pfi.price_offerings << create_price_offer( + client, 'Skin Care Package', + 'Four times a month', 250_000_000, # 250 USD + 'USD', :PER_MONTH, 'http://www.example.com/skincarepackage') + end + efi.targeted_campaign = client.path.campaign(customer_id, campaign_id) + efi.ad_schedules << create_ad_schedule_info( + client, :SUNDAY, 10, :ZERO, 18, :ZERO) + efi.ad_schedules << create_ad_schedule_info( + client, :SATURDAY, 10, :ZERO, 22, :ZERO) + end + + response = client.service.extension_feed_item.mutate_extension_feed_items( + customer_id: customer_id, + operations: [operation], + ) + + puts "Created extension feed with resource name " + + "'#{response.results.first.resource_name}'" +end + +def create_price_offer( + client, header, description, price_in_micros, + currency_code, unit, final_url, final_mobile_url=nil) + client.resource.price_offer do |po| + po.header = header + po.description = description + po.final_urls << final_url + po.price = client.resource.money do |pr| + pr.amount_micros = price_in_micros + pr.currency_code = currency_code + end + po.unit = unit + # Optional: set the final mobile URLs + unless final_mobile_url.nil? + po.final_mobile_urls << final_mobile_url + end + end +end + +def create_ad_schedule_info( + client, day_of_week, start_hour, start_minute, end_hour, end_minute) + client.resource.ad_schedule_info do |asi| + asi.day_of_week = day_of_week + asi.start_hour = start_hour + asi.start_minute = start_minute + asi.end_hour = end_hour + asi.end_minute = end_minute + end +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| + options[:campaign_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_prices(options.fetch(:customer_id).tr("-", ""), options.fetch(:campaign_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("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/extensions/add_sitelinks.rb b/examples/extensions/add_sitelinks.rb new file mode 100755 index 000000000..374fa23d3 --- /dev/null +++ b/examples/extensions/add_sitelinks.rb @@ -0,0 +1,183 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Adds sitelinks to a campaign. To create a campaign, run add_campaigns.rb. + +require 'optparse' +require 'google/ads/google_ads' +require 'date' + +def add_sitelinks(customer_id, campaign_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + campaign_resource_name = client.path.campaign(customer_id, campaign_id) + + extension_feed_items = + create_extension_feed_items(client, customer_id, campaign_resource_name) + + operation = client.operation.create_resource.campaign_extension_setting do |ces| + ces.campaign = campaign_resource_name + ces.extension_type = :SITELINK + extension_feed_items.each do |efi| + ces.extension_feed_items << efi + end + end + + response = client.service.campaign_extension_setting.mutate_campaign_extension_settings( + customer_id: customer_id, + operations: [operation], + ) + + puts "Created a campaign extension setting with resource name '#{response.results.first.resource_name}'" +end + +def create_extension_feed_items(client, customer_id, campaign_resource_name) + extension_feed_items = [ + client.resource.extension_feed_item do |efi| + efi.extension_type = :SITELINK + efi.sitelink_feed_item = create_sitelink_feed_item( + client, 'Store Hours', 'http://www.example.com/storehours') + efi.targeted_campaign = campaign_resource_name + end, + client.resource.extension_feed_item do |efi| + efi.extension_type = :SITELINK + efi.sitelink_feed_item = create_sitelink_feed_item( + client, 'Thanksgiving Specials', 'http://www.example.com/thanksgiving') + efi.targeted_campaign = campaign_resource_name + efi.start_date_time = + DateTime.new(Date.today.year, 11, 20, 0, 0, 0).strftime("%Y-%m-%d %H:%M:%S") + efi.end_date_time = + DateTime.new(Date.today.year, 11, 27, 23, 59, 59).strftime("%Y-%m-%d %H:%M:%S") + + # Targets this sitelink for United States only. + # A list of country codes can be referenced here: + # https://developers.google.com/adwords/api/docs/appendix/geotargeting + efi.targeted_geo_target_constant = client.path.geo_target_constant(2840) + end, + client.resource.extension_feed_item do |efi| + efi.extension_type = :SITELINK + efi.sitelink_feed_item = create_sitelink_feed_item( + client, 'Wifi available', 'http://www.example.com/wifi') + efi.targeted_campaign = campaign_resource_name + efi.device = :MOBILE + efi.targeted_keyword = client.resource.keyword_info do |ki| + ki.text = 'free wifi' + ki.match_type = :BROAD + end + end, + client.resource.extension_feed_item do |efi| + efi.extension_type = :SITELINK + efi.sitelink_feed_item = create_sitelink_feed_item( + client, 'Happy hours', 'http://www.example.com/happyhours') + efi.targeted_campaign = campaign_resource_name + efi.ad_schedules << create_ad_schedule(client, :MONDAY, 18, :ZERO, 21, :ZERO) + efi.ad_schedules << create_ad_schedule(client, :TUESDAY, 18, :ZERO, 21, :ZERO) + efi.ad_schedules << create_ad_schedule(client, :WEDNESDAY, 18, :ZERO, 21, :ZERO) + efi.ad_schedules << create_ad_schedule(client, :THURSDAY, 18, :ZERO, 21, :ZERO) + efi.ad_schedules << create_ad_schedule(client, :FRIDAY, 18, :ZERO, 21, :ZERO) + end + ] + + operations = extension_feed_items.map do |efi| + client.operation.create_resource.extension_feed_item(efi) + end + + response = client.service.extension_feed_item.mutate_extension_feed_items( + customer_id: customer_id, + operations: operations, + ) + + puts "Created #{response.results.size} extension feed items with the following resource names:" + response.results.map do |result| + puts "\t#{result.resource_name}" + result.resource_name + end +end + +def create_sitelink_feed_item(client, sitelink_text, sitelink_url) + client.resource.sitelink_feed_item do |sfi| + sfi.link_text = sitelink_text + sfi.final_urls << sitelink_url + end +end + +def create_ad_schedule(client, day, start_hour, start_minute, end_hour, end_minute) + client.resource.ad_schedule_info do |asi| + asi.day_of_week = day + asi.start_hour = start_hour + asi.start_minute = start_minute + asi.end_hour = end_hour + asi.end_minute = end_minute + end +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| + options[:campaign_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_sitelinks(options.fetch(:customer_id).tr("-", ""), options.fetch(:campaign_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("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/extensions/add_sitelinks_using_feeds.rb b/examples/extensions/add_sitelinks_using_feeds.rb new file mode 100755 index 000000000..28a2c3863 --- /dev/null +++ b/examples/extensions/add_sitelinks_using_feeds.rb @@ -0,0 +1,310 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Adds sitelinks to a campaign using feed services. To create a campaign, run +# add_campaigns.rb. + +require 'optparse' +require 'google/ads/google_ads' +require 'date' + +def add_sitelinks_using_feeds(customer_id, campaign_id, ad_group_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # Create a feed, which acts as a table to store data. + feed_data = create_feed(client, customer_id) + + # Create feed items, which fill out the feed table with data. + create_feed_items(client, customer_id, feed_data) + + # Create a feed mapping, which tells Google Ads how to interpret this data to + # display additional sitelink information on ads. + create_feed_mapping(client, customer_id, feed_data) + + # Create a campaign feed, which tells Google Ads which campaigns to use the + # provided data with. + create_campaign_feed(client, customer_id, campaign_id, feed_data) + + # If an ad group is specified, limit targeting only to the given ad group. + unless ad_group_id.nil? + create_ad_group_targeting(client, customer_id, feed_data, ad_group_id) + end +end + +def create_feed(client, customer_id) + feed = client.resource.feed do |f| + f.name = "Sitelinks Feed ##{(Time.new.to_f * 1000).to_i}" + f.origin = :USER + + # Specify the column name and data type. This is just raw data at this point, + # and not yet linked to any particular purpose. The names are used to help us + # remember what they are intended for later. + f.attributes << client.resource.feed_attribute do |fa| + fa.name = "Link Text" + fa.type = :STRING + end + f.attributes << client.resource.feed_attribute do |fa| + fa.name = "Link Final URL" + fa.type = :URL_LIST + end + f.attributes << client.resource.feed_attribute do |fa| + fa.name = "Line 1" + fa.type = :STRING + end + f.attributes << client.resource.feed_attribute do |fa| + fa.name = "Line 2" + fa.type = :STRING + end + end + + operation = client.operation.create_resource.feed(feed) + + response = client.service.feed.mutate_feeds( + customer_id: customer_id, + operations: [operation], + ) + + feed_resource_name = response.results.first.resource_name + + puts "Created feed with resource name '#{feed_resource_name}'" + + # After we create the feed, we need to fetch it so we can determine the + # attribute IDs, which will be required when populating feed items. + feed = client.service.google_ads.search( + customer_id: customer_id, + query: "SELECT feed.attributes FROM feed WHERE feed.resource_name = '#{feed_resource_name}'" + ).first.feed + + attribute_ids = feed.attributes.map(&:id) + + { + feed: feed_resource_name, + # The attribute IDs come back in the same order that they were added. + link_text_attribute_id: attribute_ids[0], + final_url_attribute_id: attribute_ids[1], + line_1_attribute_id: attribute_ids[2], + line_2_attribute_id: attribute_ids[3], + } +end + +def new_feed_item_operation(client, data, text, final_url, line_1, line_2) + feed_item = client.resource.feed_item do |fi| + fi.feed = data[:feed] + + fi.attribute_values << client.resource.feed_item_attribute_value do |av| + av.feed_attribute_id = data[:link_text_attribute_id] + av.string_value = text + end + fi.attribute_values << client.resource.feed_item_attribute_value do |av| + av.feed_attribute_id = data[:final_url_attribute_id] + av.string_values << final_url + end + fi.attribute_values << client.resource.feed_item_attribute_value do |av| + av.feed_attribute_id = data[:line_1_attribute_id] + av.string_value = line_1 + end + fi.attribute_values << client.resource.feed_item_attribute_value do |av| + av.feed_attribute_id = data[:line_2_attribute_id] + av.string_value = line_2 + end + end + + client.operation.create_resource.feed_item(feed_item) +end + +def create_feed_items(client, customer_id, feed_data) + operations = [] + operations << new_feed_item_operation( + client, feed_data, "Home", "http://www.example.com", + "Home line 1", "Home line 2") + operations << new_feed_item_operation( + client, feed_data, "Stores", "http://www.example.com/stores", + "Stores line 1", "Stores line 2") + operations << new_feed_item_operation( + client, feed_data, "On Sale", "http://www.example.com/sale", + "On Sale line 1", "On Sale line 2") + operations << new_feed_item_operation( + client, feed_data, "Support", "http://www.example.com/support", + "Support line 1", "Support line 2") + operations << new_feed_item_operation( + client, feed_data, "Products", "http://www.example.com/catalogue", + "Products line 1", "Products line 2") + operations << new_feed_item_operation( + client, feed_data, "About Us", "http://www.example.com/about", + "About Us line 1", "About Us line 2") + + response = client.service.feed_item.mutate_feed_items( + customer_id: customer_id, + operations: operations, + ) + + feed_items = response.results.map {|result| result.resource_name} + puts "Created the following feed items:" + feed_items.each do |feed_item| + puts "\t#{feed_item}" + end + + # We will need the resource name of the feed item to use in targeting. + feed_data[:feed_items] = feed_items + # We may also need the feed item ID if we are going to use it in a mapping function. + # For feed items, the ID is the last part of the resource name, after the '~'. + feed_data[:feed_item_ids] = feed_items.map {|feed_item| feed_item.split('~').last} +end + +def create_feed_mapping(client, customer_id, feed_data) + feed_mapping = client.resource.feed_mapping do |fm| + fm.placeholder_type = :SITELINK + fm.feed = feed_data[:feed] + + fm.attribute_field_mappings << client.resource.attribute_field_mapping do |afm| + afm.feed_attribute_id = feed_data[:link_text_attribute_id] + afm.sitelink_field = :TEXT + end + fm.attribute_field_mappings << client.resource.attribute_field_mapping do |afm| + afm.feed_attribute_id = feed_data[:final_url_attribute_id] + afm.sitelink_field = :FINAL_URLS + end + fm.attribute_field_mappings << client.resource.attribute_field_mapping do |afm| + afm.feed_attribute_id = feed_data[:line_1_attribute_id] + afm.sitelink_field = :LINE_1 + end + fm.attribute_field_mappings << client.resource.attribute_field_mapping do |afm| + afm.feed_attribute_id = feed_data[:line_2_attribute_id] + afm.sitelink_field = :LINE_2 + end + end + + operation = client.operation.create_resource.feed_mapping(feed_mapping) + + response = client.service.feed_mapping.mutate_feed_mappings( + customer_id: customer_id, + operations: [operation], + ) + + puts "Created feed mapping '#{response.results.first.resource_name}'" +end + +def create_campaign_feed(client, customer_id, campaign_id, feed_data) + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + campaign_feed = client.resource.campaign_feed do |cf| + cf.placeholder_types << :SITELINK + cf.feed = feed_data[:feed] + cf.campaign = client.path.campaign(customer_id, campaign_id) + + cf.matching_function = client.resource.matching_function do |mf| + mf.function_string = + "AND(IN(FEED_ITEM_ID,{ #{feed_data[:feed_item_ids].join(',')} }),EQUALS(CONTEXT.DEVICE,'Mobile'))" + end + end + + operation = client.operation.create_resource.campaign_feed(campaign_feed) + + response = client.service.campaign_feed.mutate_campaign_feeds( + customer_id: customer_id, + operations: [operation], + ) + + puts "Created campaign feed '#{response.results.first.resource_name}'" +end + +def create_ad_group_targeting(client, customer_id, feed_data, ad_group_id) + feed_item = feed_data[:feed_items].first + + feed_item_target = client.resource.feed_item_target do |fit| + # You must set targeting on a per-feed-item basis. This will restrict the + # first feed item we added to only serve for the given ad group. + fit.feed_item = feed_item + fit.ad_group = client.path.ad_group(customer_id, ad_group_id) + end + + operation = client.operation.create_resource.feed_item_target(feed_item_target) + + response = client.service.feed_item_target.mutate_feed_item_targets( + customer_id: customer_id, + operations: [operation], + ) + + puts "Created feed item target '#{response.results.first.resource_name}' " \ + "for feed item '#{feed_item}'" +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' + options[:ad_group_id] = nil + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| + options[:campaign_id] = v + end + + opts.on('-A', '--ad-group-id AD-GROUP-ID', String, 'Ad Group ID (optional)') do |v| + options[:ad_group_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_sitelinks_using_feeds( + options.fetch(:customer_id).tr("-", ""), + options.fetch(:campaign_id), + options.fetch(:ad_group_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("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/factories/various_factories_usage.rb b/examples/factories/various_factories_usage.rb index b96330d6d..0830ca63a 100644 --- a/examples/factories/various_factories_usage.rb +++ b/examples/factories/various_factories_usage.rb @@ -35,8 +35,8 @@ def add_campaigns(customer_id) end return_budget = client.service.campaign_budget.mutate_campaign_budgets( - customer_id, - [campaign_budget_operation] + customer_id: customer_id, + operations: [campaign_budget_operation], ) campaign_budget_resource_name = return_budget.results.first.resource_name @@ -68,8 +68,8 @@ def add_campaigns(customer_id) # Add the campaign. response = campaign_service.mutate_campaigns( - customer_id, - [campaign_operation], + customer_id: customer_id, + operations: [campaign_operation], ) campaign_resource_name = response.results.first.resource_name puts "Created campaign #{campaign_resource_name}" @@ -85,18 +85,24 @@ def add_campaigns(customer_id) ) end - campaign_service.mutate_campaigns(customer_id, [update_operation]) + campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [update_operation], + ) # Finally remove the campaign remove_op = client.operation.remove_resource.campaign(campaign_resource_name) - campaign_service.mutate_campaigns(customer_id, [remove_op]) + campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [remove_op], + ) # updates also work with only a resource name, so let's pull one out and # then update it ga_service = client.service.google_ads res = ga_service.search( - customer_id, - "select campaign.resource_name, campaign.name from campaign limit 1", + customer_id: customer_id, + query: "select campaign.resource_name, campaign.name from campaign limit 1", ) campaign_resource_name = res.first.campaign.resource_name @@ -111,7 +117,10 @@ def add_campaigns(customer_id) ) end - campaign_service.mutate_campaigns(customer_id, [update_operation]) + campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [update_operation], + ) end if __FILE__ == $0 @@ -161,10 +170,5 @@ def add_campaigns(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/factories/various_factories_usage_explicit_version.rb b/examples/factories/various_factories_usage_explicit_version.rb index 189c9b4ef..d2c211e93 100644 --- a/examples/factories/various_factories_usage_explicit_version.rb +++ b/examples/factories/various_factories_usage_explicit_version.rb @@ -35,8 +35,8 @@ def add_campaigns(customer_id) end return_budget = client.service.v1.campaign_budget.mutate_campaign_budgets( - customer_id, - [campaign_budget_operation] + customer_id: customer_id, + operations: [campaign_budget_operation], ) campaign_budget_resource_name = return_budget.results.first.resource_name @@ -68,8 +68,8 @@ def add_campaigns(customer_id) # Add the campaign. response = campaign_service.mutate_campaigns( - customer_id, - [campaign_operation], + customer_id: customer_id, + operations: [campaign_operation], ) campaign_resource_name = response.results.first.resource_name puts "Created campaign #{campaign_resource_name}" @@ -83,18 +83,24 @@ def add_campaigns(customer_id) campaign.name = "A different interplanetary Cruise #{(Time.new.to_f * 1000).to_i}" end - campaign_service.mutate_campaigns(customer_id, [update_operation]) + campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [update_operation], + ) # Finally remove the campaign remove_op = client.operation.v1.remove_resource.campaign(campaign_resource_name) - campaign_service.mutate_campaigns(customer_id, [remove_op]) + campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [remove_op], + ) # updates also work with only a resource name, so let's pull one out and # then update it ga_service = client.service.google_ads res = ga_service.search( - customer_id, - "select campaign.resource_name, campaign.name from campaign limit 1", + customer_id: customer_id, + query: "select campaign.resource_name, campaign.name from campaign limit 1", ) campaign_resource_name = res.first.campaign.resource_name @@ -107,7 +113,10 @@ def add_campaigns(customer_id) camp.name = "A different interplanetary Cruise #{(Time.new.to_f * 1000).to_i}" end - campaign_service.mutate_campaigns(customer_id, [update_operation]) + campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [update_operation], + ) end if __FILE__ == $0 @@ -157,10 +166,5 @@ def add_campaigns(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/hotel_ads/add_hotel_ad.rb b/examples/hotel_ads/add_hotel_ad.rb index 26855eecf..e11fe6f75 100644 --- a/examples/hotel_ads/add_hotel_ad.rb +++ b/examples/hotel_ads/add_hotel_ad.rb @@ -58,8 +58,10 @@ def add_campaign_budget(client, customer_id) # Issue a mutate request. campaign_budget_service = client.service.campaign_budget - response = campaign_budget_service.mutate_campaign_budgets(customer_id, - [campaign_budget_operation]) + response = campaign_budget_service.mutate_campaign_budgets( + customer_id: customer_id, + operations: [campaign_budget_operation], + ) # Fetch the new budget's resource name. budget_resource = response.results.first.resource_name @@ -105,8 +107,10 @@ def add_hotel_campaign(client, customer_id, budget_resource, # Issue a mutate request to add the campaign. campaign_service = client.service.campaign - response = campaign_service.mutate_campaigns(customer_id, - [campaign_operation]) + response = campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [campaign_operation], + ) # Fetch the new campaign's resource name. campaign_resource = response.results.first.resource_name @@ -134,8 +138,10 @@ def add_hotel_ad_group(client, customer_id, campaign_resource) # Issue a mutate request to add the ad group. ad_group_service = client.service.ad_group - response = ad_group_service.mutate_ad_groups(customer_id, - [ad_group_operation]) + response = ad_group_service.mutate_ad_groups( + customer_id: customer_id, + operations: [ad_group_operation] + ) # Fetch the new ad group's resource name. ad_group_resource = response.results.first.resource_name @@ -164,8 +170,10 @@ def add_hotel_ad_group_ad(client, customer_id, ad_group_resource) # Issue a mutate request to add the ad group ad. ad_group_ad_service = client.service.ad_group_ad - response = ad_group_ad_service.mutate_ad_group_ads(customer_id, - [ad_group_ad_operation]) + response = ad_group_ad_service.mutate_ad_group_ads( + customer_id: customer_id, + operations: [ad_group_ad_operation], + ) # Fetch the new ad group ad's resource name. ad_group_ad_resource = response.results.first.resource_name @@ -237,10 +245,5 @@ def generate_random_name_field(text) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/hotel_ads/add_hotel_ad_group_bid_modifiers.rb b/examples/hotel_ads/add_hotel_ad_group_bid_modifiers.rb index 7958b42be..b592c99a0 100644 --- a/examples/hotel_ads/add_hotel_ad_group_bid_modifiers.rb +++ b/examples/hotel_ads/add_hotel_ad_group_bid_modifiers.rb @@ -61,7 +61,9 @@ def add_hotel_ad_group_bid_modifiers(customer_id, ad_group_id) # 3) Issues a mutate request to add an ad group bid modifiers. ad_group_bid_modifier_service = client.service.ad_group_bid_modifier response = ad_group_bid_modifier_service.mutate_ad_group_bid_modifiers( - customer_id, operations) + customer_id: customer_id, + operations: operations, + ) # Print out resource names of the added ad group bid modifiers. puts "Added #{response.results.size} hotel ad group bid modifiers:" @@ -124,10 +126,5 @@ def add_hotel_ad_group_bid_modifiers(customer_id, ad_group_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_1.rb b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_1.rb index 6b31df45c..7340b01e7 100644 --- a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_1.rb +++ b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_1.rb @@ -61,7 +61,10 @@ def create_budget(client, customer_id) end campaign_budget_srv = client.service.campaign_budget - response = campaign_budget_srv.mutate_campaign_budgets(customer_id, [operation]) + response = campaign_budget_srv.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) budget_resource_name = response.results.first.resource_name puts("Created campaign budget with resource name #{budget_resource_name}") @@ -285,10 +288,5 @@ def create_keywords(adwords, ad_group_id, keywords) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_2.rb b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_2.rb index a8525a837..b3b9bf9d7 100644 --- a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_2.rb +++ b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_2.rb @@ -62,7 +62,10 @@ def create_budget(client, customer_id) end campaign_budget_srv = client.service.campaign_budget - response = campaign_budget_srv.mutate_campaign_budgets(customer_id, [operation]) + response = campaign_budget_srv.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) budget_resource_name = response.results.first.resource_name puts("Created campaign budget with resource name #{budget_resource_name}") @@ -92,7 +95,10 @@ def create_campaign(client, customer_id, budget_resource_name) end campaign_service = client.service.campaign - response = campaign_service.mutate_campaigns(customer_id, [operation]) + response = campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) campaign_resource_name = response.results.first.resource_name puts("Created campaign with id #{campaign_resource_name}") @@ -284,10 +290,5 @@ def create_keywords(adwords, ad_group_id, keywords) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_3.rb b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_3.rb index 50cb87668..b0ee40fbb 100644 --- a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_3.rb +++ b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_3.rb @@ -70,7 +70,10 @@ def create_budget(client, customer_id) end campaign_budget_srv = client.service.campaign_budget - response = campaign_budget_srv.mutate_campaign_budgets(customer_id, [operation]) + response = campaign_budget_srv.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) budget_resource_name = response.results.first.resource_name puts("Created campaign budget with resource name #{budget_resource_name}") @@ -100,7 +103,10 @@ def create_campaign(client, customer_id, budget_resource_name) end campaign_service = client.service.campaign - response = campaign_service.mutate_campaigns(customer_id, [operation]) + response = campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) campaign_resource_name = response.results.first.resource_name puts("Created campaign with id #{campaign_resource_name}") @@ -117,7 +123,10 @@ def create_ad_group(client, customer_id, campaign_resource_name) end ad_group_service = client.service.ad_group - response = ad_group_service.mutate_ad_groups(customer_id, [operation]) + response = ad_group_service.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) ad_group_resource_name = response.results.first.resource_name puts("Created ad_group with id #{ad_group_resource_name}") @@ -276,10 +285,5 @@ def create_keywords(adwords, ad_group_id, keywords) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_4.rb b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_4.rb index de1378855..3fce2319d 100644 --- a/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_4.rb +++ b/examples/migration/campaign_management/create_complete_campaign_both_apis_phase_4.rb @@ -72,7 +72,10 @@ def create_budget(client, customer_id) end campaign_budget_srv = client.service.campaign_budget - response = campaign_budget_srv.mutate_campaign_budgets(customer_id, [operation]) + response = campaign_budget_srv.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) budget_resource_name = response.results.first.resource_name puts("Created campaign budget with resource name #{budget_resource_name}") @@ -102,7 +105,10 @@ def create_campaign(client, customer_id, budget_resource_name) end campaign_service = client.service.campaign - response = campaign_service.mutate_campaigns(customer_id, [operation]) + response = campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) campaign_resource_name = response.results.first.resource_name puts("Created campaign with id #{campaign_resource_name}") @@ -119,7 +125,10 @@ def create_ad_group(client, customer_id, campaign_resource_name) end ad_group_service = client.service.ad_group - response = ad_group_service.mutate_ad_groups(customer_id, [operation]) + response = ad_group_service.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) ad_group_resource_name = response.results.first.resource_name puts("Created ad_group with id #{ad_group_resource_name}") @@ -154,8 +163,8 @@ def create_text_ads(client, customer_id, ad_group_resource_name) ad_group_ad_service = client.service.ad_group_ad response = ad_group_ad_service.mutate_ad_group_ads( - customer_id, - operations + customer_id: customer_id, + operations: operations, ) ad_group_ad_resource_names = response.results.map(&:resource_name) @@ -280,10 +289,5 @@ def create_keywords(adwords, ad_group_id, keywords) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/migration/campaign_management/create_complete_campaign_google_ads_api_only.rb b/examples/migration/campaign_management/create_complete_campaign_google_ads_api_only.rb index 536c2467c..bcec06567 100644 --- a/examples/migration/campaign_management/create_complete_campaign_google_ads_api_only.rb +++ b/examples/migration/campaign_management/create_complete_campaign_google_ads_api_only.rb @@ -61,7 +61,10 @@ def create_budget(client, customer_id) end campaign_budget_srv = client.service.campaign_budget - response = campaign_budget_srv.mutate_campaign_budgets(customer_id, [operation]) + response = campaign_budget_srv.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) budget_resource_name = response.results.first.resource_name puts("Created campaign budget with resource name #{budget_resource_name}") @@ -91,7 +94,10 @@ def create_campaign(client, customer_id, budget_resource_name) end campaign_service = client.service.campaign - response = campaign_service.mutate_campaigns(customer_id, [operation]) + response = campaign_service.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) campaign_resource_name = response.results.first.resource_name puts("Created campaign with id #{campaign_resource_name}") @@ -108,7 +114,10 @@ def create_ad_group(client, customer_id, campaign_resource_name) end ad_group_service = client.service.ad_group - response = ad_group_service.mutate_ad_groups(customer_id, [operation]) + response = ad_group_service.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) ad_group_resource_name = response.results.first.resource_name puts("Created ad_group with id #{ad_group_resource_name}") @@ -143,8 +152,8 @@ def create_text_ads(client, customer_id, ad_group_resource_name) ad_group_ad_service = client.service.ad_group_ad response = ad_group_ad_service.mutate_ad_group_ads( - customer_id, - operations + customer_id: customer_id, + operations: operations, ) ad_group_ad_resource_names = response.results.map(&:resource_name) @@ -171,7 +180,10 @@ def create_keywords(client, customer_id, ad_group_resource_name, keywords) } criterion_service = client.service.ad_group_criterion - response = criterion_service.mutate_ad_group_criteria(customer_id, operations) + response = criterion_service.mutate_ad_group_criteria( + customer_id: customer_id, + operations: operations, + ) criteria_resource_names = response.results.map(&:resource_name) @@ -237,10 +249,5 @@ def create_keywords(client, customer_id, ad_group_resource_name, keywords) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/misc/get_all_image_assets.rb b/examples/misc/get_all_image_assets.rb new file mode 100644 index 000000000..b911fb2aa --- /dev/null +++ b/examples/misc/get_all_image_assets.rb @@ -0,0 +1,104 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This code example gets all image assets. + +require 'optparse' +require 'google/ads/google_ads' + +def get_all_image_assets(customer_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + query = <<~EOQUERY + SELECT asset.name, asset.image_asset.file_size, + asset.image_asset.full_size.width_pixels, + asset.image_asset.full_size.height_pixels, + asset.image_asset.full_size.url FROM asset + WHERE asset.type = IMAGE + EOQUERY + + ga_service = client.service.google_ads + response = ga_service.search(customer_id: customer_id, query: query) + + count = 0 + response.each do |row| + asset = row.asset + image_asset = asset.image_asset + count += 1 + puts( + "Image with name #{asset.name} found:\n"\ + "\tfile size #{image_asset.file_size} bytes\n"\ + "\twidth #{image_asset.full_size.width_pixels}px\n"\ + "\theight #{image_asset.full_size.height_pixels}px\n"\ + "\turl '#{image_asset.full_size.url}'" + ) + end + puts("Total of #{count} image asset#{if count > 1 then 's' else '' end} found") +end + + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: add_campaigns.rb [options]') + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + get_all_image_assets(options.fetch(:customer_id).tr('-', '')) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf('Error with message: %s\n', error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf('\tOn field: %s\n', field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf('\tType: %s\n\tCode: %s\n', k, v) + end + end + raise + end +end diff --git a/examples/misc/get_all_videos_and_images.rb b/examples/misc/get_all_videos_and_images.rb index 676ee8432..1f96ecd91 100644 --- a/examples/misc/get_all_videos_and_images.rb +++ b/examples/misc/get_all_videos_and_images.rb @@ -32,12 +32,12 @@ def get_all_videos_and_images(customer_id) EOQUERY ga_service = client.service.google_ads - response = ga_service.search(customer_id, query) + response = ga_service.search(customer_id: customer_id, query: query) response.each do |row| puts( - "Media file with id: #{row.media_file.id.value}"\ - ", name: #{row.media_file.name.value}, and type: #{row.media_file.type}" + "Media file with id: #{row.media_file.id}"\ + ", name: #{row.media_file.name}, and type: #{row.media_file.type}" ) end end @@ -90,10 +90,5 @@ def get_all_videos_and_images(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/misc/upload_image.rb b/examples/misc/upload_image.rb index 08a463917..6ec9f4858 100644 --- a/examples/misc/upload_image.rb +++ b/examples/misc/upload_image.rb @@ -28,7 +28,7 @@ def upload_image(customer_id) # ENV['HOME']/google_ads_config.rb when called without parameters client = Google::Ads::GoogleAds::GoogleAdsClient.new - op = client.operation.create_resource.media_file do |media_file| + operation = client.operation.create_resource.media_file do |media_file| media_file.type = :IMAGE media_file.image = client.resource.media_image do |media_image| media_image.data = image_data @@ -36,7 +36,10 @@ def upload_image(customer_id) end media_file_service = client.service.media_file - response = media_file_service.mutate_media_files(customer_id, [op]) + response = media_file_service.mutate_media_files( + customer_id: customer_id, + operations: [operation], + ) puts("Uploaded media file with id: #{response.results.first.resource_name}") end @@ -88,10 +91,5 @@ def upload_image(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/misc/upload_image_asset.rb b/examples/misc/upload_image_asset.rb new file mode 100755 index 000000000..8294cac9d --- /dev/null +++ b/examples/misc/upload_image_asset.rb @@ -0,0 +1,111 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This code example uploads an image asset. +# To get image assets, run get_all_image_assets.rb. + +require 'optparse' +require 'google/ads/google_ads' +require 'open-uri' + +def upload_image_asset(customer_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + url = 'https://goo.gl/3b9Wfh' + image_data = open(url) { |f| f.read } + + # Create the operation for uploading the image asset. + asset_operation = client.operation.create_resource.asset do |asset| + asset.type = :IMAGE + asset.image_asset = client.resource.image_asset do |image_asset| + image_asset.data = image_data + image_asset.file_size = image_data.length() + image_asset.mime_type = :IMAGE_JPEG + image_asset.full_size = client.resource.image_dimension do |dimension| + dimension.height_pixels = 315 + dimension.width_pixels = 600 + dimension.url = url + end + # Optional: Provide a unique friendly name to identify your asset. + # If you specify the name field, then both the asset name and the image + # being uploaded should be unique, and should not match another ACTIVE + # asset in this customer account. + # image_asset.name = 'Jupiter Trip #' + SecureRandom.uuid + end + end + + # Upload the image asset. + response = client.service.asset.mutate_assets( + customer_id: customer_id, + operations: [asset_operation], + ) + + puts "Uploaded image asset #{response.results.first.resource_name}." +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + upload_image_asset(options.fetch(:customer_id).tr('-', '')) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf('Error with message: %s\n', error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf('\tOn field: %s\n', field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf('\tType: %s\n\tCode: %s\n', k, v) + end + end + raise + end +end + diff --git a/examples/planning/add_keyword_plan.rb b/examples/planning/add_keyword_plan.rb index b1a5262bb..75ef5a0e0 100755 --- a/examples/planning/add_keyword_plan.rb +++ b/examples/planning/add_keyword_plan.rb @@ -51,7 +51,10 @@ def create_keyword_plan(client, customer_id) end keyword_plan_service = client.service.keyword_plan - response = keyword_plan_service.mutate_keyword_plans(customer_id, [operation]) + response = keyword_plan_service.mutate_keyword_plans( + customer_id: customer_id, + operations: [operation], + ) resource_name = response.results.first.resource_name puts "Created keyword plan: #{resource_name}" @@ -75,8 +78,8 @@ def create_keyword_plan_campaign(client, customer_id, keyword_plan) kp_campaign_service_client = client.service.keyword_plan_campaign response = kp_campaign_service_client.mutate_keyword_plan_campaigns( - customer_id, - [operation], + customer_id: customer_id, + operations: [operation], ) resource_name = response.results.first.resource_name @@ -94,8 +97,8 @@ def create_keyword_plan_ad_group(client, customer_id, plan_campaign) kp_ad_group_service = client.service.keyword_plan_ad_group response = kp_ad_group_service.mutate_keyword_plan_ad_groups( - customer_id, - [operation], + customer_id: customer_id, + operations: [operation], ) resource_name = response.results.first.resource_name @@ -132,8 +135,8 @@ def create_keyword_plan_ad_group_keywords(client, customer_id, plan_ad_group) kpa_keyword_service = client.service.keyword_plan_ad_group_keyword response = kpa_keyword_service.mutate_keyword_plan_ad_group_keywords( - customer_id, - operations, + customer_id: customer_id, + operations: operations, ) response.results.each do |result| @@ -151,8 +154,8 @@ def create_keyword_plan_negative_campaign_keywords(client, customer_id, plan_cam kp_campaign_keyword_service = client.service.keyword_plan_campaign_keyword response = kp_campaign_keyword_service.mutate_keyword_plan_campaign_keywords( - customer_id, - [operation], + customer_id: customer_id, + operations: [operation], ) puts "Created negative campaign keyword for keyword plan: " + @@ -208,10 +211,5 @@ def create_keyword_plan_negative_campaign_keywords(client, customer_id, plan_cam end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/planning/generate_forecast_metrics.rb b/examples/planning/generate_forecast_metrics.rb index 70e438c23..1e32605eb 100755 --- a/examples/planning/generate_forecast_metrics.rb +++ b/examples/planning/generate_forecast_metrics.rb @@ -28,7 +28,7 @@ def generate_forecast_metrics(customer_id, keyword_plan_id) kp_service = client.service.keyword_plan response = kp_service.generate_forecast_metrics( - client.path.keyword_plan(customer_id, keyword_plan_id) + keyword_plan: client.path.keyword_plan(customer_id, keyword_plan_id), ) response.keyword_forecasts.each_with_index do |forecast, i| @@ -113,10 +113,5 @@ def generate_forecast_metrics(customer_id, keyword_plan_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/planning/generate_keyword_ideas.rb b/examples/planning/generate_keyword_ideas.rb index 29f113145..225c90765 100755 --- a/examples/planning/generate_keyword_ideas.rb +++ b/examples/planning/generate_keyword_ideas.rb @@ -63,13 +63,13 @@ def generate_keyword_ideas(customer_id, location_ids, language_id, keywords, include_adult_keywords = true response = kp_idea_service.generate_keyword_ideas( - customer_id, - client.wrapper.string(client.path.language_constant(language_id)), - geo_target_constants, - include_adult_keywords, + customer_id: customer_id, + language: client.wrapper.string(client.path.language_constant(language_id)), + geo_target_constants: geo_target_constants, + include_adult_keywords: include_adult_keywords, # To restrict to only Google Search, change the parameter below to # :GOOGLE_SEARCH - :GOOGLE_SEARCH_AND_PARTNERS, + keyword_plan_network: :GOOGLE_SEARCH_AND_PARTNERS, **options_hash ) @@ -172,10 +172,5 @@ def generate_keyword_ideas(customer_id, location_ids, language_id, keywords, end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/recommendations/apply_recommendation.rb b/examples/recommendations/apply_recommendation.rb index fad44fcde..1cc7214c4 100644 --- a/examples/recommendations/apply_recommendation.rb +++ b/examples/recommendations/apply_recommendation.rb @@ -46,8 +46,10 @@ def apply_recommendation(customer_id, recommendation_id) # Issues a mutate request to apply the recommendation. recommendation_service = client.service.recommendation - response = recommendation_service.apply_recommendation(customer_id, - [apply_recommendation_operation]) + response = recommendation_service.apply_recommendation( + customer_id: customer_id, + operations: [apply_recommendation_operation], + ) applied_recommendation = response.results.first puts "Applied recommendation with resource name: '#{applied_recommendation.resource_name}'." @@ -112,10 +114,5 @@ def apply_recommendation(customer_id, recommendation_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/recommendations/dismiss_recommendation.rb b/examples/recommendations/dismiss_recommendation.rb index 72fb420d5..a1b509c1b 100755 --- a/examples/recommendations/dismiss_recommendation.rb +++ b/examples/recommendations/dismiss_recommendation.rb @@ -34,8 +34,8 @@ def dismiss_recommendation(customer_id, recommendation_id) # Issues a mutate request to dismiss the recommendation. recommendation_service = client.service.recommendation response = recommendation_service.dismiss_recommendation( - customer_id, - [dismiss_recommendation_operation] + customer_id: customer_id, + operations: [dismiss_recommendation_operation], ) dismissed_recommendation = response.results.first @@ -105,10 +105,5 @@ def dismiss_recommendation(customer_id, recommendation_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/recommendations/get_text_ad_recommendations.rb b/examples/recommendations/get_text_ad_recommendations.rb index ffb4d7e5c..46be82e44 100755 --- a/examples/recommendations/get_text_ad_recommendations.rb +++ b/examples/recommendations/get_text_ad_recommendations.rb @@ -34,7 +34,11 @@ def get_text_ad_recommendations(customer_id) WHERE recommendation.type = TEXT_AD QUERY - response = ga_service.search(customer_id, query, page_size: PAGE_SIZE) + response = ga_service.search( + customer_id: customer_id, + query: query, + page_size: PAGE_SIZE, + ) response.each do |row| recommendation = row.recommendation @@ -108,10 +112,5 @@ def get_text_ad_recommendations(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/remarketing/add_conversion_action.rb b/examples/remarketing/add_conversion_action.rb index 10351b151..cfdc296c4 100644 --- a/examples/remarketing/add_conversion_action.rb +++ b/examples/remarketing/add_conversion_action.rb @@ -47,7 +47,9 @@ def add_conversion_action(customer_id) # Add the ad group ad. response = client.service.conversion_action.mutate_conversion_actions( - customer_id, [conversion_action_operation]) + customer_id: customer_id, + operations: [conversion_action_operation], + ) puts "New conversion action with resource name = #{response.results.first.resource_name}." end @@ -99,11 +101,5 @@ def add_conversion_action(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end - diff --git a/examples/remarketing/add_merchant_center_dynamic_remarketing_campaign.rb b/examples/remarketing/add_merchant_center_dynamic_remarketing_campaign.rb new file mode 100755 index 000000000..117afa0fe --- /dev/null +++ b/examples/remarketing/add_merchant_center_dynamic_remarketing_campaign.rb @@ -0,0 +1,300 @@ +#!/usr/bin/env ruby + +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example creates a shopping campaign associated with an existing merchant +# center account, along with a related ad group and dynamic display ad, and +# targets a user list for remarketing purposes. + +require "optparse" +require "date" +require "open-uri" +require "google/ads/google_ads" + +def add_merchant_center_dynamic_remarketing_campaign( + customer_id, + merchant_center_id, + campaign_budget_id, + user_list_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 shopping campaign associated with a given merchant center account. + campaign_resource_name = create_campaign( + client, + customer_id, + merchant_center_id, + campaign_budget_id + ) + + # Creates an ad group for the campaign. + ad_group_resource_name = create_ad_group(client, customer_id, + campaign_resource_name) + + # Creates a dynamic display ad in the ad group. + create_ad(client, customer_id, ad_group_resource_name) + + # Targets a specific user list for remarketing. + attach_user_list(client, customer_id, ad_group_resource_name, user_list_id) +end + +# Creates a campaign linked to a Merchant Center product feed. +def create_campaign(client, customer_id, merchant_center_id, campaign_budget_id) + operation = client.operation.create_resource.campaign do |c| + c.name = "Shopping campaign ##{(Time.new.to_f * 1000).to_i}" + + # Dynamic remarketing campaigns are only available on the Google Display + # Network. + c.advertising_channel_type = :DISPLAY + c.status = :PAUSED + c.campaign_budget = client.path.campaign_budget(customer_id, + campaign_budget_id) + c.manual_cpc = client.resource.manual_cpc + + # The settings for the shopping campaign. + # This connects the campaign to the merchant center account. + c.shopping_setting = client.resource.shopping_setting do |ss| + ss.campaign_priority = 0 + ss.merchant_id = merchant_center_id + + # Display Network campaigns do not support partition by country. The only + # supported value is "ZZ". This signals that products from all countries + # are available in the campaign. The actual products which serve are based + # on the products tagged in the user list entry. + ss.sales_country = "ZZ" + ss.enable_local = true + end + end + + response = client.service.campaign.mutate_campaigns( + customer_id: customer_id, + operations: [operation] + ) + + puts "Created campaign: #{response.results.first.resource_name}" + response.results.first.resource_name +end + +# Creates an ad group for the remarketing campaign. +def create_ad_group(client, customer_id, campaign_resource_name) + # Creates the ad group. + ad_group = client.resource.ad_group do |ag| + ag.name = "Dynamic remarketing ad group #{(Time.now.to_f * 1000).to_i}" + ag.campaign = campaign_resource_name + ag.status = :ENABLED + end + + # Creates the ad group operation. + operation = client.operation.create_resource.ad_group(ad_group) + response = client.service.ad_group.mutate_ad_groups( + customer_id: customer_id, + operations: [operation] + ) + + puts "Created ad group: #{response.results.first.resource_name}" + response.results.first.resource_name +end + +# Creates the responsive display ad. +def create_ad(client, customer_id, ad_group_resource_name) + marketing_image_url = "https://goo.gl/3b9Wfh" + square_marketing_image_url = "https://goo.gl/mtt54n" + marketing_image_asset_resource_name = upload_asset( + client, customer_id, marketing_image_url, "Marketing Image" + ) + square_marketing_image_asset_resource_name = upload_asset( + client, customer_id, square_marketing_image_url, "Square Marketing Image" + ) + + # Creates an ad group ad operation. + operation = client.operation.create_resource.ad_group_ad do |aga| + aga.ad_group = ad_group_resource_name + aga.status = :PAUSED + aga.ad = client.resource.ad do |a| + a.final_urls << "https://www.example.com" + + # Creates the responsive display ad info object. + a.responsive_display_ad = client.resource.responsive_display_ad_info do |rda| + rda.headlines << client.resource.ad_text_asset do |ata| + ata.text = "Travel" + end + rda.long_headline = client.resource.ad_text_asset do |ata| + ata.text = "Travel the World" + end + rda.descriptions << client.resource.ad_text_asset do |ata| + ata.text = "Take to the air!" + end + rda.business_name = "Interplanetary Cruises" + rda.marketing_images << client.resource.ad_image_asset do |aia| + aia.asset = marketing_image_asset_resource_name + end + rda.square_marketing_images << client.resource.ad_image_asset do |aia| + aia.asset = square_marketing_image_asset_resource_name + end + # Optional: Call to action text. + # Valid texts: https://support.google.com/adwords/answer/7005917 + rda.call_to_action_text = "Apply Now" + # Optional: Sets the ad colors. + rda.main_color = "#0000ff" + rda.accent_color = "#ffff00" + # Optional: Sets to false to strictly render the ad using the colors. + rda.allow_flexible_color = false + # Optional: Sets the format setting that the ad will be served in. + rda.format_setting = :NON_NATIVE + # Optional: Creates a logo image and sets it to the ad. + # rda.logo_images << client.resource.ad_image_asset do |aia| + # aia.asset = "INSERT_LOGO_IMAGE_RESOURCE_NAME_HERE" + # end + # Optional: Creates a square logo image and sets it to the ad. + # rda.square_logo_images << client.resource.ad_image_asset do |aia| + # aia.asset = "INSERT_SQUARE_LOGO_IMAGE_RESOURCE_NAME_HERE" + # end + end + end + end + + # Issues a mutate request to add the ad group ad. + response = client.service.ad_group_ad.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation] + ) + + # Prints out some information about the newly created ad. + resource_name = response.results.first.resource_name + puts "Created ad group ad: #{resource_name}" + + resource_name +end + +# Adds an image to the Google Ads account. +def upload_asset(client, customer_id, image_url, image_name) + # Creates an asset operation. + operation = client.operation.create_resource.asset do |a| + a.name = image_name + a.type = :IMAGE + a.image_asset = client.resource.image_asset do |image| + image.data = open(image_url) { |f| f.read } + end + end + + # Issues a mutate request to add the asset. + response = client.service.asset.mutate_assets( + customer_id: customer_id, + operations: [operation] + ) + + # Prints out information about the newly added asset. + resource_name = response.results.first.resource_name + puts "Created image asset: #{resource_name}" + + resource_name +end + +# Targets a user list. +def attach_user_list(client, customer_id, ad_group_resource_name, user_list_id) + user_list_resource_name = client.path.user_list(customer_id, user_list_id) + + # Creates the ad group criterion that targets the user list. + ad_group_criterion = client.resource.ad_group_criterion do |agc| + agc.ad_group = ad_group_resource_name + agc.user_list = client.resource.user_list_info do |ul| + ul.user_list = user_list_resource_name + end + end + + # Creates the ad group criterion operation. + op = client.operation.create_resource.ad_group_criterion(ad_group_criterion) + + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: [op] + ) + + puts "Created ad group criterion: #{response.results.first.resource_name}" +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = "INSERT_CUSTOMER_ID_HERE" + options[:merchant_center_id] = "INSERT_MERCHANT_CENTER_ACCOUNT_ID_HERE" + options[:campaign_budget_id] = "INSERT_CAMPAIGN_BUDGET_ID_HERE" + options[:user_list_id] = "INSERT_USER_LIST_ID_HERE" + + 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("-m", "--merchant-center-account-id MERCHANT-CENTER-ACCOUNT-ID", + String, "Merchant Center Account ID") do |v| + options[:merchant_center_id] = v + end + + opts.on("-b", "--campaign-budget-id CAMPAIGN-BUDGET-ID", String, + "Campaign Budget ID") do |v| + options[:campaign_budget_id] = v + end + + opts.on("-u", "--user-list-id USER-LIST-ID", String, "User List ID") do |v| + options[:user_list_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_merchant_center_dynamic_remarketing_campaign( + options.fetch(:customer_id).tr("-", ""), + options.fetch(:merchant_center_id), + options.fetch(:campaign_budget_id), + options.fetch(:user_list_id) + ) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + error.location&.field_path_elements&.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/remarketing/add_remarketing_action.rb b/examples/remarketing/add_remarketing_action.rb new file mode 100644 index 000000000..5de5c7daa --- /dev/null +++ b/examples/remarketing/add_remarketing_action.rb @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example adds a new remarketing action to the customer and then retrieves +# its associated tag snippets. + +require 'optparse' +require 'google/ads/google_ads' +require 'date' + +def add_remarketing_action(customer_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # Step 1: Create a remarketing action. + operation = client.operation.create_resource.remarketing_action do |action| + action.name = "Remarketing action ##{(Time.new.to_f * 100).to_i}" + end + + response = client.service.remarketing_action.mutate_remarketing_actions( + customer_id: customer_id, + operations: [operation], + ) + + remarketing_action_resource_name = response.results.first.resource_name + + # Step 2: Look up the remarketing action we created to get some extra + # information about it, like its tag snippets. + query = <<~EOQUERY + SELECT + remarketing_action.id, + remarketing_action.name, + remarketing_action.tag_snippets + FROM + remarketing_action + WHERE + remarketing_action.resource_name = "#{remarketing_action_resource_name}" + EOQUERY + + response = client.service.google_ads.search( + customer_id: customer_id, + query: query, + ) + + action = response.first.remarketing_action + puts "Remarking action has ID #{action.id} and name '#{action.name}.'" + puts "It has the following generated tag snippets:" + action.tag_snippets.each do |ts| + puts "Tag snippet with code type '#{ts.type}' and code page format " \ + "'#{ts.page_format}' has the following global site tag:\n#{ts.global_site_tag}" + puts "and the following event snippet:\n#{ts.event_snippet}" + end +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__)) + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + add_remarketing_action(options.fetch(:customer_id).tr("-", "")) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + if error.location + error.location.field_path_elements.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end + diff --git a/examples/remarketing/upload_conversion_adjustment.rb b/examples/remarketing/upload_conversion_adjustment.rb new file mode 100755 index 000000000..622b5f24e --- /dev/null +++ b/examples/remarketing/upload_conversion_adjustment.rb @@ -0,0 +1,182 @@ +#!/usr/bin/env ruby + +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example imports conversion adjustments for conversions that already exist. +# To set up a conversion action, run add_conversion_action.rb. + +require 'optparse' +require 'google/ads/google_ads' + +def upload_conversion_adjustment( + customer_id, + conversion_action_id, + gclid, + adjustment_type, + conversion_date_time, + adjustment_date_time, + restatement_value +) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # Associate conversion adjustments with the existing conversion action. + # The GCLID should have been uploaded before with a conversion. + conversion_adjustment = client.resource.conversion_adjustment do |ca| + ca.conversion_action = client.path.conversion_action(customer_id, conversion_action_id) + ca.adjustment_type = adjustment_type + ca.gclid_date_time_pair = client.resource.gclid_date_time_pair do |gdtp| + gdtp.gclid = gclid + gdtp.conversion_date_time = conversion_date_time + end + ca.adjustment_date_time = adjustment_date_time + + # Set adjusted value for adjustment type RESTATEMENT. + if adjustment_type == :RESTATEMENT + ca.restatement_value = client.resource.restatement_value do |ra| + ra.adjusted_value = restatement_value.to_f + end + end + end + + # Issue a request to upload the conversion adjustment(s). + response = client.service.conversion_adjustment_upload.upload_conversion_adjustments( + customer_id: customer_id, + # This example shows just one adjustment but you may upload multiple ones. + conversion_adjustments: [conversion_adjustment], + partial_failure: true + ) + + if response.partial_failure_error.nil? + # Process and print all results for multiple adjustments + response.results.each do |result| + puts "Uploaded conversion adjustment at #{result.adjustment_date_time} " \ + "for adjustment #{result.adjustment_type} to #{result.conversion_action}." + end + else + # Print any partial errors returned. + failures = client.decode_partial_failure_error(response.partial_failure_error) + puts 'Request failed. Failure details:' + failures.each do |failure| + failure.errors.each do |error| + puts "\t#{error.error_code.error_code}: #{error.message}" + end + end + end +end + +if __FILE__ == $0 + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:conversion_action_id] = 'INSERT_CONVERSION_ACTION_ID_HERE' + options[:gclid] = 'INSERT_GCLID_HERE' + # RETRACTION negates a conversion, and RESTATEMENT changes the value of a + # conversion. + options[:adjustment_type] = 'INSERT_ADJUSTMENT_TYPE_HERE' + options[:conversion_date_time] = 'INSERT_CONVERSION_DATE_TIME_HERE' + options[:adjustment_date_time] = 'INSERT_ADJUSTMENT_DATE_TIME_HERE' + # Optional: Specify an adjusted value below for adjustment type RESTATEMENT. + # This value will be ignored if you specify RETRACTION as adjustment type. + options[:restatement_value] = 'INSERT_RESTATEMENT_VALUE_HERE' + + 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('-c', '--conversion-action-id CONVERSION-ACTION-ID', String, + 'Conversion Action ID') do |v| + options[:conversion_action_id] = v + end + + opts.on('-g', '--gclid GCLID', String, + 'Google Click ID (should be newer than the number of days set on '\ + 'the conversion window of the conversion action).') do |v| + options[:gclid] = v + end + + opts.on('-a', '--adjustment-type ADJUSTMENT-TYPE', String, + 'Adjustment Type, e.g. RETRACTION, RESTATEMENT') do |v| + options[:adjustment_type] = v + end + + opts.on('-o', '--conversion-date-time CONVERSION-DATE-TIME', String, + 'The date and time of the conversion (should be after click time).'\ + ' The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", '\ + 'e.g. "2019-01-01 12:32:45-08:00".') do |v| + options[:conversion_date_time] = v + end + + opts.on('-A', '--adjustment-date-time ADJUSTMENT-DATE-TIME', String, + 'The date and time of the adjustment. '\ + 'The format is "yyyy-mm-dd hh:mm:ss+|-hh:mm", '\ + 'e.g. "2019-01-01 12:32:45-08:00".') do |v| + options[:adjustment_date_time] = v + end + + opts.on('-r', '--restatement-value RESTATEMENT-VALUE', String, + '[optional] The adjusted value for adjustment type RESTATEMENT.') do |v| + options[:restatement_value] = v + end + + opts.separator '' + opts.separator 'Help:' + + opts.on_tail('-h', '--help', 'Show this message') do + puts opts + exit + end + end.parse! + + begin + upload_conversion_adjustment( + options.fetch(:customer_id).tr('-', ''), + options.fetch(:conversion_action_id), + options.fetch(:gclid), + options.fetch(:adjustment_type).to_sym, + options.fetch(:conversion_date_time), + options.fetch(:adjustment_date_time), + options.fetch(:restatement_value) + ) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + error.location&.field_path_elements&.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/reporting/campaign_report_to_csv.rb b/examples/reporting/campaign_report_to_csv.rb index 2ba427c0d..e1e9cbc62 100644 --- a/examples/reporting/campaign_report_to_csv.rb +++ b/examples/reporting/campaign_report_to_csv.rb @@ -56,7 +56,7 @@ def write_campaign_report_csv(customer_id, target_filepath) ORDER BY segments.date DESC QUERY - response = ga_service.search(customer_id, query, page_size: PAGE_SIZE) + response = ga_service.search(customer_id: customer_id, query: query, page_size: PAGE_SIZE) # convert the Google Ads response rows in to CSV ready hash objects csv_rows = response.map { |row| result_row_as_csv_hash(row) } @@ -89,7 +89,7 @@ def write_campaign_report_csv(customer_id, target_filepath) # code. # # Running the example with -h will print the command line usage. - options[:customer_id] = 'INSERT_ADWORDS_CUSTOMER_ID_HERE' + options[:customer_id] = 'INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE' options[:output_file_path] = __FILE__.gsub(".rb", ".csv") OptionParser.new do |opts| opts.banner = sprintf('Usage: ruby %s [options]', File.basename(__FILE__)) @@ -135,10 +135,5 @@ def write_campaign_report_csv(customer_id, target_filepath) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/reporting/get_hotel_ads_performance.rb b/examples/reporting/get_hotel_ads_performance.rb index 1eb239e79..e8921bd4f 100755 --- a/examples/reporting/get_hotel_ads_performance.rb +++ b/examples/reporting/get_hotel_ads_performance.rb @@ -48,7 +48,7 @@ def get_hotel_ads_performance(customer_id) LIMIT 50 QUERY - response = ga_service.search(customer_id, query, page_size: PAGE_SIZE) + response = ga_service.search(customer_id: customer_id, query: query, page_size: PAGE_SIZE) if response.response.results.empty? puts sprintf("The given query returned no entries:\n %s", query) @@ -119,10 +119,5 @@ def get_hotel_ads_performance(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/reporting/get_keyword_stats.rb b/examples/reporting/get_keyword_stats.rb index 838ea9f23..93edbdd90 100755 --- a/examples/reporting/get_keyword_stats.rb +++ b/examples/reporting/get_keyword_stats.rb @@ -51,7 +51,7 @@ def get_keyword_stats(customer_id) LIMIT 50 QUERY - responses = ga_service.search_stream(customer_id, query) + responses = ga_service.search_stream(customer_id: customer_id, query: query) responses.each do |response| response.results.each do |row| @@ -83,7 +83,7 @@ def get_keyword_stats(customer_id) # code. # # Running the example with -h will print the command line usage. - options[:customer_id] = 'INSERT_ADWORDS_CUSTOMER_ID_HERE' + options[:customer_id] = 'INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE' OptionParser.new do |opts| opts.banner = sprintf('Usage: ruby %s [options]', File.basename(__FILE__)) @@ -120,10 +120,5 @@ def get_keyword_stats(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/shopping_ads/add_listing_scope.rb b/examples/shopping_ads/add_listing_scope.rb new file mode 100755 index 000000000..f1390a03b --- /dev/null +++ b/examples/shopping_ads/add_listing_scope.rb @@ -0,0 +1,152 @@ +#!/usr/bin/env ruby + +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This example shows how to add a shopping listing scope to a shopping campaign. +# The example will construct and add a new listing scope which will act as the +# inventory filter for the campaign. The campaign will only advertise products +# that match the following requirements: + +# - Brand is "google" +# - Custom label 0 is "top_selling_products" +# - Product type (level 1) is "electronics" +# - Product type (level 2) is "smartphones" +# +# Only one listing scope is allowed per campaign. Remove any existing listing +# scopes before running this example. + +require 'optparse' +require 'google/ads/google_ads' + +def add_listing_scope(customer_id, campaign_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + # A listing scope allows you to filter the products that will be included in + # a given campaign. You can specify multiple dimensions with conditions that + # must be met for a product to be included in a campaign. + # A typical listing scope might only have a few dimensions. This example + # demonstrates a range of different dimensions you could use. + operation = client.operation.create_resource.campaign_criterion do |cc| + cc.campaign = client.path.campaign(customer_id, campaign_id) + + cc.listing_scope = client.resource.listing_scope_info do |lsi| + # Creates a ProductBrand dimension set to "google". + lsi.dimensions << client.resource.listing_dimension_info do |ldi| + ldi.product_brand = client.resource.product_brand_info do |pbi| + pbi.value = 'google' + end + end + + # Creates a ProductCustomAttribute dimension for INDEX0 set to "top_selling_products". + lsi.dimensions << client.resource.listing_dimension_info do |ldi| + ldi.product_custom_attribute = client.resource.product_custom_attribute_info do |pcai| + pcai.value = 'top_selling_products' + pcai.index = :INDEX0 + end + end + + # Creates a ProductType dimension for LEVEL1 set to "electronics". + lsi.dimensions << client.resource.listing_dimension_info do |ldi| + ldi.product_type = client.resource.product_type_info do |pti| + pti.value = 'electronics' + pti.level = :LEVEL1 + end + end + + # Creates a ProductType dimension for LEVEL2 set to "smartphones". + lsi.dimensions << client.resource.listing_dimension_info do |ldi| + ldi.product_type = client.resource.product_type_info do |pti| + pti.value = 'smartphones' + pti.level = :LEVEL2 + end + end + end + end + + # Issue the mutate request. + response = client.service.campaign_criterion.mutate_campaign_criteria( + customer_id: customer_id, + operations: [operation] + ) + puts "Added #{response.results.size} campaign criteria:" + response.results.each do |resource| + puts "#{resource.resource_name}" + end +end + +if __FILE__ == $0 + PAGE_SIZE = 1000 + + options = {} + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE' + options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' + # Specifying any value for this field on the command line will override this + # to true. + options[:should_replace_existing_tree] = false + + OptionParser.new do |opts| + opts.banner = sprintf("Usage: #{File.basename(__FILE__)} [options]") + + opts.separator '' + opts.separator 'Options:' + + opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v| + options[:customer_id] = v + end + + opts.on('-c', '--campaign-id CAMPAIGN-ID', String, 'Campaign ID') do |v| + options[:campaign_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_listing_scope( + options.fetch(:customer_id).tr('-', ''), + options.fetch(:campaign_id) + ) + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + e.failure.errors.each do |error| + STDERR.printf("Error with message: %s\n", error.message) + error.location&.field_path_elements&.each do |field_path_element| + STDERR.printf("\tOn field: %s\n", field_path_element.field_name) + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/shopping_ads/add_shopping_product_ad.rb b/examples/shopping_ads/add_shopping_product_ad.rb index ae5f59014..dd5fdd825 100755 --- a/examples/shopping_ads/add_shopping_product_ad.rb +++ b/examples/shopping_ads/add_shopping_product_ad.rb @@ -66,7 +66,10 @@ def add_campaign_budget(client, customer_id) end service = client.service.campaign_budget - response = service.mutate_campaign_budgets(customer_id, [operation]) + response = service.mutate_campaign_budgets( + customer_id: customer_id, + operations: [operation], + ) budget_name = response.results.first.resource_name @@ -101,7 +104,10 @@ def add_standard_shopping_campaign( end service = client.service.campaign - response = service.mutate_campaigns(customer_id, [operation]) + response = service.mutate_campaigns( + customer_id: customer_id, + operations: [operation], + ) campaign_name = response.results.first.resource_name @@ -120,7 +126,10 @@ def add_shopping_product_ad_group(client, customer_id, campaign_name) end service = client.service.ad_group - response = service.mutate_ad_groups(customer_id, [operation]) + response = service.mutate_ad_groups( + customer_id: customer_id, + operations: [operation], + ) ad_group_name = response.results.first.resource_name @@ -140,7 +149,10 @@ def add_shopping_product_ad_group_ad(client, customer_id, ad_group_name) end service = client.service.ad_group_ad - response = service.mutate_ad_group_ads(customer_id, [operation]) + response = service.mutate_ad_group_ads( + customer_id: customer_id, + operations: [operation], + ) puts "Created shopping product ad group ad " \ "#{response.results.first.resource_name}" @@ -230,10 +242,5 @@ def add_default_shopping_listing_group(client, customer_id, ad_group_name) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/shopping_ads/add_shopping_product_listing_group_tree.rb b/examples/shopping_ads/add_shopping_product_listing_group_tree.rb index 958109ab3..8aac47206 100755 --- a/examples/shopping_ads/add_shopping_product_listing_group_tree.rb +++ b/examples/shopping_ads/add_shopping_product_listing_group_tree.rb @@ -130,8 +130,8 @@ def add_shopping_product_listing_group_tree( # * Brand: CoolBrand # * CPC bid: $0.90 listing_dimension_info = client.resource.listing_dimension_info do |ldi| - listing_dimension_info.product_brand = client.resource.product_brand_info do |product_brand_info| - product_brand_info.value = client.wrapper.string("CoolBrand") + ldi.product_brand = client.resource.product_brand_info do |pbi| + pbi.value = client.wrapper.string("CoolBrand") end end @@ -152,8 +152,8 @@ def add_shopping_product_listing_group_tree( # * Brand: CheapBrand # * CPC bid: $0.01 listing_dimension_info = client.resource.listing_dimension_info do |ldi| - listing_dimension_info.product_brand = client.resource.product_brand_info do |product_brand_info| - product_brand_info.value = client.wrapper.string("CheapBrand") + ldi.product_brand = client.resource.product_brand_info do |pbi| + pbi.value = client.wrapper.string("CheapBrand") end end ad_group_criterion_brand_cheap_brand = create_listing_group_unit_biddable( @@ -172,7 +172,7 @@ def add_shopping_product_listing_group_tree( # Biddable Unit node: (Brand other node) # * CPC bid: $0.05 listing_dimension_info = client.resource.listing_dimension_info do |ldi| - listing_dimension_info.product_brand = client.resource.product_brand_info + ldi.product_brand = client.resource.product_brand_info end ad_group_criterion_brand_other_brand = create_listing_group_unit_biddable( client, @@ -188,8 +188,10 @@ def add_shopping_product_listing_group_tree( operations << operation # Issue the mutate request. - agc_service = client.service.ad_group_criterion - response = agc_service.mutate_ad_group_criteria(customer_id, operations) + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: operations, + ) total_count = 0 response.results.each do |added_criterion| @@ -215,7 +217,7 @@ def remove_listing_group_tree(client, customer_id, ad_group_id) ad_group.id = #{ad_group_id} QUERY - response = ga_service.search(customer_id, query, page_size: PAGE_SIZE) + response = ga_service.search(customer_id: customer_id, query: query, page_size: PAGE_SIZE) operations = response.map do |row| criterion = row.ad_group_criterion @@ -225,8 +227,10 @@ def remove_listing_group_tree(client, customer_id, ad_group_id) end if operations.any? - agc_service = client.service.ad_group_criterion - response = agc_service.mutate_ad_group_criteria(customer_id, operations) + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: operations, + ) puts "Removed #{response.results.count} ad group criteria." end end @@ -355,10 +359,5 @@ def create_listing_group_unit_biddable(client, customer_id, ad_group_id, end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/shopping_ads/get_product_bidding_category_constant.rb b/examples/shopping_ads/get_product_bidding_category_constant.rb index 18e1c5666..d713b5b37 100644 --- a/examples/shopping_ads/get_product_bidding_category_constant.rb +++ b/examples/shopping_ads/get_product_bidding_category_constant.rb @@ -49,8 +49,8 @@ def get_product_bidding_category_constant(customer_id) ga_service = client.service.google_ads response = ga_service.search( - customer_id, - query, + customer_id: customer_id, + query: query, page_size: PAGE_SIZE, ) @@ -137,10 +137,5 @@ def get_product_bidding_category_constant(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/targeting/add_campaign_targeting_criteria.rb b/examples/targeting/add_campaign_targeting_criteria.rb index c6c6276a5..f963fa6ba 100755 --- a/examples/targeting/add_campaign_targeting_criteria.rb +++ b/examples/targeting/add_campaign_targeting_criteria.rb @@ -33,7 +33,10 @@ def add_campaign_targeting_criteria(customer_id, campaign_id, keyword, location_ operations = [negative_keyword, location, proximity] - response = criteria_service.mutate_campaign_criteria(customer_id, operations) + response = criteria_service.mutate_campaign_criteria( + customer_id: customer_id, + operations: operations + ) response.results.each do |resource| puts sprintf("Added campaign criterion %s", resource.resource_name) end @@ -76,7 +79,7 @@ def create_negative_keyword(client, customer_id, campaign_id, keyword) criterion.campaign = client.path.campaign(customer_id, campaign_id) criterion.negative = true criterion.keyword = client.resource.keyword_info do |ki| - ki.text = client.wrapper.string(keyword) + ki.text = keyword ki.match_type = :BROAD end end @@ -93,7 +96,7 @@ def create_negative_keyword(client, customer_id, campaign_id, keyword) # code. # # Running the example with -h will print the command line usage. - options[:customer_id] = 'INSERT_ADWORDS_CUSTOMER_ID_HERE' + options[:customer_id] = 'INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE' options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' options[:keyword] = 'jupiter cruise' # For more information on determining location_id value, see: @@ -148,10 +151,5 @@ def create_negative_keyword(client, customer_id, campaign_id, keyword) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/targeting/add_customer_negative_criteria.rb b/examples/targeting/add_customer_negative_criteria.rb index e0c287d78..8ea9b12e5 100644 --- a/examples/targeting/add_customer_negative_criteria.rb +++ b/examples/targeting/add_customer_negative_criteria.rb @@ -44,8 +44,8 @@ def add_customer_negative_criteria(customer_id) customer_negative_criterion_service = client.service.customer_negative_criterion response = customer_negative_criterion_service.mutate_customer_negative_criteria( - customer_id, - ops, + customer_id: customer_id, + operations: ops, ) # Display the results. @@ -103,10 +103,5 @@ def add_customer_negative_criteria(customer_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/targeting/add_demographic_targeting_criteria.rb b/examples/targeting/add_demographic_targeting_criteria.rb new file mode 100755 index 000000000..70717e03b --- /dev/null +++ b/examples/targeting/add_demographic_targeting_criteria.rb @@ -0,0 +1,115 @@ +#!/usr/bin/env ruby +# Encoding: utf-8 +# +# Copyright:: Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This code example adds demographic target criteria to an ad group, one as +# positive ad group criterion and one as negative ad group criterion. To create +# ad groups, run add_ad_groups.rb. + +require 'optparse' +require 'google/ads/google_ads' + +def add_demographic_targeting_criteria(customer_id, ad_group_id) + # GoogleAdsClient will read a config file from + # ENV['HOME']/google_ads_config.rb when called without parameters + client = Google::Ads::GoogleAds::GoogleAdsClient.new + + ad_group_resource = client.path.ad_group(customer_id, ad_group_id) + operations = [] + + # Create a positive ad group criterion for gender MALE. + operations << client.operation.create_resource.ad_group_criterion do |agc| + agc.ad_group = ad_group_resource + agc.gender = client.resource.gender_info do |gi| + gi.type = :MALE + end + end + + # Create a negative ad group criterion for age range of 18 to 24. + operations << client.operation.create_resource.ad_group_criterion do |agc| + agc.ad_group = ad_group_resource + agc.negative = true + agc.age_range = client.resource.age_range_info do |ari| + ari.type = :AGE_RANGE_18_24 + end + end + + response = client.service.ad_group_criterion.mutate_ad_group_criteria( + customer_id: customer_id, + operations: operations, + ) + + response.results.each do |result| + puts "Created ad group criterion '#{result.resource_name}'." + end +end + +if __FILE__ == $PROGRAM_NAME + options = {} + + # The following parameter(s) should be provided to run the example. You can + # either specify these by changing the INSERT_XXX_ID_HERE values below, or on + # the command line. + # + # Parameters passed on the command line will override any parameters set in + # code. + # + # Running the example with -h will print the command line usage. + options[:customer_id] = 'INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE' + options[:ad_group_id] = 'INSERT_AD_GROUP_ID_HERE' + + OptionParser.new do |opts| + opts.banner = sprintf('Usage: ruby %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('-A', '--ad-group-id AD-GROUP-ID', String, 'Ad Group ID') do |v| + options[:ad_group_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_demographic_targeting_criteria(options.fetch(:customer_id).tr("-", ""), + options[:ad_group_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("\tOn field: %s\n", field_path_element.field_name) + end + end + error.error_code.to_h.each do |k, v| + next if v == :UNSPECIFIED + STDERR.printf("\tType: %s\n\tCode: %s\n", k, v) + end + end + raise + end +end diff --git a/examples/targeting/get_campaign_targeting_criteria.rb b/examples/targeting/get_campaign_targeting_criteria.rb index df9265a62..8caf1e9a5 100755 --- a/examples/targeting/get_campaign_targeting_criteria.rb +++ b/examples/targeting/get_campaign_targeting_criteria.rb @@ -37,11 +37,14 @@ def get_campaign_targeting_criteria(customer_id, campaign_id) campaign_criterion.keyword.text, campaign_criterion.keyword.match_type FROM campaign_criterion - WHERE campaign.id = %s + WHERE campaign.id = #{campaign_id} QUERY - response = ga_service.search(customer_id, sprintf(query, campaign_id), - page_size: PAGE_SIZE) + response = ga_service.search( + customer_id: customer_id, + query: query, + page_size: PAGE_SIZE + ) response.each do |row| criterion = row.campaign_criterion @@ -50,7 +53,7 @@ def get_campaign_targeting_criteria(customer_id, campaign_id) if criterion.type == :KEYWORD puts sprintf("\t%sKeyword with text '%s' and match type %s.", - criterion.negative.value ? "Negative " : "", + criterion.negative ? "Negative " : "", criterion.keyword.text, criterion.keyword.match_type) else @@ -72,7 +75,7 @@ def get_campaign_targeting_criteria(customer_id, campaign_id) # code. # # Running the example with -h will print the command line usage. - options[:customer_id] = 'INSERT_ADWORDS_CUSTOMER_ID_HERE' + options[:customer_id] = 'INSERT_GOOGLE_ADS_CUSTOMER_ID_HERE' options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE' OptionParser.new do |opts| @@ -115,10 +118,5 @@ def get_campaign_targeting_criteria(customer_id, campaign_id) end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/examples/targeting/get_geo_target_constants_by_names.rb b/examples/targeting/get_geo_target_constants_by_names.rb index 6e972dfb8..9a5cb06a2 100755 --- a/examples/targeting/get_geo_target_constants_by_names.rb +++ b/examples/targeting/get_geo_target_constants_by_names.rb @@ -42,8 +42,11 @@ def get_geo_target_constants_by_names # https://developers.google.com/adwords/api/docs/appendix/geotargeting country_code = 'FR' - response = gtc_service.suggest_geo_target_constants(locale, country_code, - location_names: location_names) + response = gtc_service.suggest_geo_target_constants( + locale: locale, + country_code: country_code, + location_names: location_names + ) response.geo_target_constant_suggestions.each do |suggestion| puts sprintf("%s (%s,%s,%s,%s) is found in locale (%s) with reach (%d)" \ @@ -90,10 +93,5 @@ def get_geo_target_constants_by_names end end raise - rescue Google::Gax::RetryError => e - STDERR.printf("Error: '%s'\n\tCause: '%s'\n\tCode: %d\n\tDetails: '%s'\n" \ - "\tRequest-Id: '%s'\n", e.message, e.cause.message, e.cause.code, - e.cause.details, e.cause.metadata['request-id']) - raise end end diff --git a/google-ads-googleads.gemspec b/google-ads-googleads.gemspec index 0e9d05d90..8d7739b2f 100644 --- a/google-ads-googleads.gemspec +++ b/google-ads-googleads.gemspec @@ -34,8 +34,8 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.5.0' s.summary = 'Google client library for the Google Ads API' - s.add_dependency 'google-gax', '~> 1.6' - s.add_dependency 'google-protobuf', '3.11.4' + s.add_dependency 'gapic-common', '0.2.0' + s.add_dependency 'google-protobuf', '~> 3.12' s.add_development_dependency 'bundler', ["> 1.9", "< 3"] s.add_development_dependency 'rake', '~> 11.3' diff --git a/lib/google/ads/google_ads.rb b/lib/google/ads/google_ads.rb index 933b17c08..03a528b1a 100644 --- a/lib/google/ads/google_ads.rb +++ b/lib/google/ads/google_ads.rb @@ -17,13 +17,3 @@ require 'google/ads/google_ads/api_versions' require 'google/ads/google_ads/google_ads_client' - -module Google - module Ads - module GoogleAds - def self.valid_version?(version) - known_api_versions.include?(version) - end - end - end -end diff --git a/lib/google/ads/google_ads/api_versions.rb b/lib/google/ads/google_ads/api_versions.rb index 019bd7390..8edbc44e2 100644 --- a/lib/google/ads/google_ads/api_versions.rb +++ b/lib/google/ads/google_ads/api_versions.rb @@ -1,8 +1,8 @@ module Google module Ads module GoogleAds - KNOWN_API_VERSIONS = [:V1, :V2, :V3, :V4] - DEFAULT_API_VERSION = :V4 + KNOWN_API_VERSIONS = [:V2, :V3, :V4, :V5] + DEFAULT_API_VERSION = :V5 def self.default_api_version DEFAULT_API_VERSION @@ -11,6 +11,10 @@ def self.default_api_version def self.known_api_versions KNOWN_API_VERSIONS end + + def self.valid_version?(version) + known_api_versions.include?(version) + end end end end diff --git a/lib/google/ads/google_ads/autoboxing_mappings.rb b/lib/google/ads/google_ads/autoboxing_mappings.rb index 17a336969..d7ea528a5 100644 --- a/lib/google/ads/google_ads/autoboxing_mappings.rb +++ b/lib/google/ads/google_ads/autoboxing_mappings.rb @@ -36,6 +36,7 @@ def self.wrap_once(mappings) raise ArgumentError.new("Value #{x} is not boolish") end }, + Google::Protobuf::FloatValue => lambda { |x| Float(x) }, Google::Protobuf::DoubleValue => lambda { |x| Float(x) }, Google::Protobuf::BytesValue => lambda { |x| x.force_encoding("ASCII-8BIT") }, }) diff --git a/lib/google/ads/google_ads/error_transformer.rb b/lib/google/ads/google_ads/error_transformer.rb deleted file mode 100644 index 8b50e89a2..000000000 --- a/lib/google/ads/google_ads/error_transformer.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Google - module Ads - module GoogleAds - ERROR_TRANSFORMER = Proc.new do |gax_error| - begin - gax_error.status_details.each do |detail| - # If there is an underlying GoogleAdsFailure, throw that one. - if detail.class.name.start_with?("Google::Ads::GoogleAds") && - detail.class.name.end_with?("GoogleAdsFailure") - raise Google::Ads::GoogleAds::Errors::GoogleAdsError.new( - detail - ) - elsif detail.is_a?(Google::Protobuf::Any) - type = Google::Protobuf::DescriptorPool.generated_pool.lookup( - detail.type_name - ).msgclass - failure = detail.unpack(type) - - raise Google::Ads::GoogleAds::Errors::GoogleAdsError.new( - failure - ) - end - end - rescue Google::Ads::GoogleAds::Errors::GoogleAdsError - # If we raised this, bubble it out. - raise - rescue NoMethodError - # Sometimes status_details is just a String; in that case, we should - # just raise the original exception. - end - # If we don't find an error of the correct type, or if we run into an - # error while processing, just throw the original. - raise gax_error - end - end - end -end diff --git a/lib/google/ads/google_ads/errors.rb b/lib/google/ads/google_ads/errors.rb index bc1f4e744..c16a4555b 100644 --- a/lib/google/ads/google_ads/errors.rb +++ b/lib/google/ads/google_ads/errors.rb @@ -75,7 +75,28 @@ def self.message(error) # and extracts error code in the form of a hash # # Returns a Hash { name:, value:} - def self.code(error, version = Google::Ads::GoogleAds.default_api_version) + def self.code(error, version = nil) + error_version = error.class.name.split("::")[3] + if error_version.nil? + raise RuntimeError, "passed error is not a google ads class" + end + + error_version = error_version.upcase.to_sym + + if version != nil + Deprecation.new(false, false).deprecate( + "Passing explicit versions to #code is deprecated, instead" \ + " we now infer it from the passed object." + ) + end + + if version != nil && error_version.to_s != version.to_s + raise ArgumentError, + "passed version must match verison class of error" + end + + version = error_version + mapping = ERROR_CODES_MAPPING.fetch(version) match = mapping.find do |error_name| error.error_code.send(error_name) != :UNSPECIFIED diff --git a/lib/google/ads/google_ads/google_ads_client.rb b/lib/google/ads/google_ads/google_ads_client.rb index f22f7fba3..9b8bca51b 100644 --- a/lib/google/ads/google_ads/google_ads_client.rb +++ b/lib/google/ads/google_ads/google_ads_client.rb @@ -22,7 +22,7 @@ module Google module Ads module GoogleAds - module V1 + module V2 module Common end module Enums @@ -34,7 +34,7 @@ module Resources module Services end end - module V2 + module V3 module Common end module Enums @@ -46,7 +46,7 @@ module Resources module Services end end - module V3 + module V4 module Common end module Enums @@ -58,7 +58,7 @@ module Resources module Services end end - module V4 + module V5 module Common end module Enums @@ -85,14 +85,13 @@ module Services require 'google/ads/google_ads/field_mask_util' require 'google/ads/google_ads/lookup_util' require 'google/ads/google_ads/wrapper_util' -require 'google/ads/google_ads/logging_interceptor' +require 'google/ads/google_ads/interceptors/logging_interceptor' require 'google/ads/google_ads/factories' require 'google/ads/google_ads/errors' require 'google/ads/google_ads/service_lookup' require 'google/ads/google_ads/deprecation' -require 'google/ads/google_ads/v1/services/google_ads_service_client' -require 'google/gax' +require 'grpc' require 'logger' require 'json' @@ -160,21 +159,26 @@ def configure(&block) # # Raises ArgumentError if no service can be found for the provided type. def service - service_path = ENV['GOOGLEADS_SERVICE_PATH'] - ServiceLookup.new( lookup_util, - service_path, @logger, @config, make_channel, + endpoint, + deprecator, ).call end - def make_channel + def endpoint + target.split(":443").first + end + + def target default_target = "googleads.googleapis.com:443" target = ENV.fetch('GOOGLEADS_SERVICE_PATH', default_target) + end + def make_channel channel_args = { MAX_MESSAGE_LENGTH => 64*1024*1024, MAX_METADATA_SIZE => 16*1024*1024, @@ -299,11 +303,14 @@ def default_api_version end def deprecate(deprecation) - @deprecation ||= Google::Ads::GoogleAds::Deprecation.new( + deprecator.deprecate(deprecation) + end + + def deprecator + Google::Ads::GoogleAds::Deprecation.new( @config.treat_deprecation_warnings_as_errors, @config.warn_on_all_deprecations, ) - @deprecation.deprecate(deprecation) end end end diff --git a/lib/google/ads/google_ads/interceptors/error_interceptor.rb b/lib/google/ads/google_ads/interceptors/error_interceptor.rb new file mode 100644 index 000000000..73a1fd6ce --- /dev/null +++ b/lib/google/ads/google_ads/interceptors/error_interceptor.rb @@ -0,0 +1,84 @@ +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Interceptor to transform error responses to GoogleAdsErrors. + +require 'grpc/generic/interceptors' + +module Google + module Ads + module GoogleAds + module Interceptors + class ErrorInterceptor < GRPC::ClientInterceptor + ERROR_TRANSFORMER = lambda do |gax_error| + begin + gax_error.status_details.each do |detail| + # If there is an underlying GoogleAdsFailure, throw that one. + if detail.class.name.start_with?("Google::Ads::GoogleAds") && + detail.class.name.end_with?("GoogleAdsFailure") + return Google::Ads::GoogleAds::Errors::GoogleAdsError.new( + detail + ) + elsif detail.is_a?(Google::Protobuf::Any) + type = Google::Protobuf::DescriptorPool.generated_pool.lookup( + detail.type_name + ).msgclass + failure = detail.unpack(type) + + return Google::Ads::GoogleAds::Errors::GoogleAdsError.new( + failure + ) + end + end + rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e + return e + rescue NoMethodError + # Sometimes status_details is just a String; in that case, we should + # just raise the original exception. + end + # If we don't find an error of the correct type, or if we run into an + # error while processing, just throw the original. + gax_error + end + + def initialize() + # Don't propagate args, parens are necessary + super() + @error_transformer = ERROR_TRANSFORMER + end + + def request_response(request:, call:, method:, metadata: {}) + begin + yield + rescue Exception => e + raise @error_transformer.call(e) + end + end + + def server_streamer(request:, call:, method:, metadata: {}) + responses = yield + Enumerator.new do |y| + responses.each { |response| y << response } + rescue BasicObject => e + raise @error_transformer.call(e) + end + end + end + end + end + end +end + diff --git a/lib/google/ads/google_ads/interceptors/logging_interceptor.rb b/lib/google/ads/google_ads/interceptors/logging_interceptor.rb new file mode 100644 index 000000000..a119ffdee --- /dev/null +++ b/lib/google/ads/google_ads/interceptors/logging_interceptor.rb @@ -0,0 +1,220 @@ +# Encoding: utf-8 +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Interceptor to log outgoing requests and incoming responses. + +require 'google/ads/google_ads/api_versions' +require 'google/ads/google_ads/partial_failure_error_decoder' +require 'grpc/generic/interceptors' +require 'json' + +module Google + module Ads + module GoogleAds + module Interceptors + class LoggingInterceptor < GRPC::ClientInterceptor + + def initialize(logger) + # Don't propagate args, parens are necessary + super() + @logger = logger + end + + def request_response(request:, call:, method:, metadata: {}) + begin + response = yield + + @logger.info { build_summary_message(request, call, method, false) } + @logger.debug { build_request_message(metadata, request) } + @logger.debug { build_success_response_message(response) } + if response.respond_to?(:partial_failure_error) && response.partial_failure_error + @logger.debug { build_partial_failure_message(response) } + end + response + rescue Exception + @logger.warn { build_summary_message(request, call, method, true) } + @logger.info { build_request_message(metadata, request) } + @logger.info { build_error_response_message } + raise + end + end + + def server_streamer(request:, call:, method:, metadata: {}) + begin + @logger.info { build_summary_message(request, call, method, false) } + responses = yield + Enumerator.new do |y| + responses.each { |response| + @logger.debug { build_request_message(metadata, request) } + @logger.debug { build_success_response_message(response) } + if response.respond_to?(:partial_failure_error) && response.partial_failure_error + @logger.debug { build_partial_failure_message(response) } + end + y << response + } + @logger.debug { + request_id = call + .instance_variable_get(:@wrapped) + .instance_variable_get(:@call) + .trailing_metadata["request-id"] + "Request ID for preceding streaming request: #{request_id}" + } + rescue Exception + handle_error(request, call, method, metadata) + end + rescue Exception + handle_error(request, call, method, metadata) + end + end + + private + + def handle_error(request, call, method, metadata) + @logger.warn { build_summary_message(request, call, method, true) } + @logger.info { build_request_message(metadata, request) } + @logger.info { build_error_response_message } + raise + end + + def build_partial_failure_message(response) + errors = PartialFailureErrorDecoder.decode( + response.partial_failure_error + ) + errors.reduce("Partial failure errors: ") do |accum, error| + accum += error.to_json + "\n" + accum + end + end + + def build_error_response_message + exception = $! + response_message = "" + response_message << "Incoming response (errors): \n" + response_message << error_details(exception).join + response_message + end + + def error_details(exception) + last_user_line = CallerFilter.first_non_google_ads_line + exception_details = [ + " ", + exception.class, + "(", + exception.message, + "): " + ] + + case exception + when Google::Ads::GoogleAds::Errors::GoogleAdsError + exception.failure.errors.each do |error| + exception_details << error.message + end + when GRPC::InvalidArgument + exception_details << exception.details + end + + exception_details << [ + "\n", + " called from: ", + last_user_line + ] + + exception_details + end + + def build_success_response_message(response) + "Incoming response: Payload: #{response.to_json}" + end + + def build_request_message(metadata, request) + # calling #to_json on some protos (specifically those with non-UTF8 + # encodable byte values) causes a segfault, however #inspect works + # so we check if the proto contains a bytevalue, and if it does + # we #inspect instead of #to_json + request_inspect = if use_bytes_inspect?(request) + request.inspect + else + request.to_json + end + "Outgoing request: Headers: #{metadata.to_json} Payload: #{request_inspect}" + end + + def build_summary_message(request, call, method, is_fault) + customer_id = "N/A" + customer_id = request.customer_id if request.respond_to?(:customer_id) + # CustomerService get requests have a different format. + if request.respond_to?(:resource_name) + customer_id = request.resource_name.split('/').last + end + + is_fault_string = if is_fault + "yes" + else + "no" + end + + [ + "CID: #{customer_id}", + "Host: #{call.instance_variable_get('@wrapped').peer}", + "Method: #{method}", + "IsFault: #{is_fault_string}", + ].join(", ") + end + + def response_error_from_detail(detail) + detail.errors.map.with_index { |error, i| + "Error #{i + 1}: #{error.to_json}" + }.join("\n") + end + + def use_bytes_inspect?(request) + @cycle_finder = CycleFinder.new + contains_bytes_field?(request.class.descriptor) + end + + def contains_bytes_field?(descriptor) + return false if descriptor.nil? + return false if @cycle_finder.is_cycle?(descriptor) + + @cycle_finder.add_object(descriptor) + + descriptor.map { |x| x.type == :bytes || (x.type == :message && contains_bytes_field?(x.subtype)) }.any? + end + + def interesting_error_classes + @interesting_error_classes ||= Google::Ads::GoogleAds::Errors.namespaces.map do |namespace| + namespace.const_get(:GoogleAdsFailure) + end + end + + class CycleFinder + def initialize + @objects_seen = Set.new + end + + def is_cycle?(object) + @objects_seen.include?(object.object_id) + end + + def add_object(object) + @objects_seen.add(object.object_id) + end + end + end + end + end + end +end diff --git a/lib/google/ads/google_ads/logging_interceptor.rb b/lib/google/ads/google_ads/logging_interceptor.rb deleted file mode 100644 index 38eb18758..000000000 --- a/lib/google/ads/google_ads/logging_interceptor.rb +++ /dev/null @@ -1,199 +0,0 @@ -# Encoding: utf-8 -# -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Interceptor to log outgoing requests and incoming responses. - -require 'google/ads/google_ads/api_versions' -require 'google/ads/google_ads/partial_failure_error_decoder' -require 'grpc/generic/interceptors' -require 'json' - -module Google - module Ads - module GoogleAds - class LoggingInterceptor < GRPC::ClientInterceptor - - def initialize(logger) - # Don't propagate args, parens are necessary - super() - @logger = logger - end - - def request_response(request:, call:, method:, metadata: {}) - begin - response = yield - - @logger.info { build_summary_message(request, call, method, false) } - @logger.debug { build_request_message(metadata, request) } - @logger.debug { build_success_response_message(response) } - if response.respond_to?(:partial_failure_error) && response.partial_failure_error - @logger.debug { build_partial_failure_message(response) } - end - response - rescue Exception - @logger.warn { build_summary_message(request, call, method, true) } - @logger.info { build_request_message(metadata, request) } - @logger.info { build_error_response_message } - raise - end - end - - def server_streamer(request:, call:, method:, metadata: {}) - begin - @logger.info { build_summary_message(request, call, method, false) } - responses = yield - Enumerator.new do |y| - responses.each { |response| - @logger.debug { build_request_message(metadata, request) } - @logger.debug { build_success_response_message(response) } - if response.respond_to?(:partial_failure_error) && response.partial_failure_error - @logger.debug { build_partial_failure_message(response) } - end - y << response - } - @logger.debug { - "Request ID for preceding streaming request: " \ - "#{call.instance_variable_get(:@wrapped).instance_variable_get(:@call).trailing_metadata["request-id"]}" - } - end - rescue Exception - @logger.warn { build_summary_message(request, call, method, true) } - @logger.info { build_request_message(metadata, request) } - @logger.info { build_error_response_message } - raise - end - end - - private - - def build_partial_failure_message(response) - errors = PartialFailureErrorDecoder.decode( - response.partial_failure_error - ) - errors.reduce("Partial failure errors: ") do |accum, error| - accum += error.to_json + "\n" - accum - end - end - - def build_error_response_message - # this looks like "magic", but the Google::Gax::GaxError grabs - # the current exception as its cause, and then parses details - # out of that exception. So this sets it up so that - # most_recent_error.status_details contains useful information about - # our failure - most_recent_error = Google::Gax::GaxError.new('') - response_message = "Incoming response (errors): \n" - - formatted_details = case most_recent_error.status_details - when nil - "" - when String - most_recent_error.status_details - when Array - most_recent_error.status_details.select { |detail| - interesting_error_classes.include?(detail.class) - }.map { |detail| - response_error_from_detail(detail) - }.join("\n") - end - - response_message += formatted_details - response_message - end - - def build_success_response_message(response) - "Incoming response: Payload: #{response.to_json}" - end - - def build_request_message(metadata, request) - # calling #to_json on some protos (specifically those with non-UTF8 - # encodable byte values) causes a segfault, however #inspect works - # so we check if the proto contains a bytevalue, and if it does - # we #inspect instead of #to_json - request_inspect = if use_bytes_inspect?(request) - request.inspect - else - request.to_json - end - "Outgoing request: Headers: #{metadata.to_json} Payload: #{request_inspect}" - end - - def build_summary_message(request, call, method, is_fault) - customer_id = "N/A" - customer_id = request.customer_id if request.respond_to?(:customer_id) - # CustomerService get requests have a different format. - if request.respond_to?(:resource_name) - customer_id = request.resource_name.split('/').last - end - - is_fault_string = if is_fault - "yes" - else - "no" - end - - [ - "CID: #{customer_id}", - "Host: #{call.instance_variable_get('@wrapped').peer}", - "Method: #{method}", - "IsFault: #{is_fault_string}", - ].join(", ") - end - - def response_error_from_detail(detail) - detail.errors.map.with_index { |error, i| - "Error #{i + 1}: #{error.to_json}" - }.join("\n") - end - - def use_bytes_inspect?(request) - @cycle_finder = CycleFinder.new - contains_bytes_field?(request.class.descriptor) - end - - def contains_bytes_field?(descriptor) - return false if descriptor.nil? - return false if @cycle_finder.is_cycle?(descriptor) - - @cycle_finder.add_object(descriptor) - - descriptor.map { |x| x.type == :bytes || (x.type == :message && contains_bytes_field?(x.subtype)) }.any? - end - - def interesting_error_classes - @interesting_error_classes ||= Google::Ads::GoogleAds::Errors.namespaces.map do |namespace| - namespace.const_get(:GoogleAdsFailure) - end - end - - class CycleFinder - def initialize - @objects_seen = Set.new - end - - def is_cycle?(object) - @objects_seen.include?(object.object_id) - end - - def add_object(object) - @objects_seen.add(object.object_id) - end - end - end - end - end -end diff --git a/lib/google/ads/google_ads/lookup_util.rb b/lib/google/ads/google_ads/lookup_util.rb index eac466efd..f8d050f8b 100644 --- a/lib/google/ads/google_ads/lookup_util.rb +++ b/lib/google/ads/google_ads/lookup_util.rb @@ -1,3 +1,4 @@ +require 'google/ads/google_ads/api_versions' module Google module Ads module GoogleAds diff --git a/lib/google/ads/google_ads/partial_failure_error_decoder.rb b/lib/google/ads/google_ads/partial_failure_error_decoder.rb index 560fb2869..8ee0847f3 100644 --- a/lib/google/ads/google_ads/partial_failure_error_decoder.rb +++ b/lib/google/ads/google_ads/partial_failure_error_decoder.rb @@ -1,3 +1,5 @@ +require 'google/protobuf/well_known_types' + module Google module Ads module GoogleAds diff --git a/lib/google/ads/google_ads/search_stream_intercepting_factory.rb b/lib/google/ads/google_ads/search_stream_intercepting_factory.rb deleted file mode 100644 index 97773f255..000000000 --- a/lib/google/ads/google_ads/search_stream_intercepting_factory.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'delegate' - -module Google - module Ads - module GoogleAds - class SearchStreamInterceptingFactory < SimpleDelegator - def initialize(transformer, factory) - @transformer = transformer - @factory = factory - - super(@factory) - end - - def google_ads - GoogleAdsInterceptor.new(@transformer, super) - end - - private - - class GoogleAdsInterceptor < SimpleDelegator - def initialize(transformer, google_ads_service) - @transformer = transformer - @google_ads_service = google_ads_service - - super(@google_ads_service) - end - - def search_stream(*args, &blk) - res = super(*args) - if !blk.nil? - begin - res.each(&blk) - return res - rescue BasicObject => e - raise @transformer.call(e) - end - end - - Enumerator.new do |y| - res.each { |item| y << item } - rescue BasicObject => e - raise @transformer.call(e) - end - end - end - end - end - end -end diff --git a/lib/google/ads/google_ads/service_lookup.rb b/lib/google/ads/google_ads/service_lookup.rb index 9ee5130d5..58a40c3ca 100644 --- a/lib/google/ads/google_ads/service_lookup.rb +++ b/lib/google/ads/google_ads/service_lookup.rb @@ -1,50 +1,53 @@ -require 'google/ads/google_ads/error_transformer' -require 'google/ads/google_ads/search_stream_intercepting_factory' +require 'google/ads/google_ads/interceptors/logging_interceptor' +require 'google/ads/google_ads/interceptors/error_interceptor' module Google module Ads module GoogleAds class ServiceLookup - def initialize(lookup_util, service_path, logger, config, credentials_or_channel) + def initialize( + lookup_util, + logger, + config, + credentials_or_channel, + endpoint, + deprecator + ) @lookup_util = lookup_util - @service_path = service_path @logger = logger @config = config @credentials_or_channel = credentials_or_channel + @endpoint = endpoint + @deprecator = deprecator end def call if logger - logging_interceptor = Google::Ads::GoogleAds::LoggingInterceptor.new(logger) + logging_interceptor = GoogleAds::Interceptors::LoggingInterceptor.new(logger) end + error_interceptor = GoogleAds::Interceptors::ErrorInterceptor.new version_alternates = {} Factories::VERSIONS.each do |v| - version_alternates[v] = factory_at_version(v, logging_interceptor) + version_alternates[v] = factory_at_version(v, error_interceptor, logging_interceptor) end highest_factory = factory_at_version( Factories::HIGHEST_VERSION, + error_interceptor, logging_interceptor, ) - highest_factory = if Factories::HIGHEST_VERSION == :V3 - GoogleAds::SearchStreamInterceptingFactory.new( - GoogleAds::ERROR_TRANSFORMER, - highest_factory, - ) - else - highest_factory - end VersionAlternate.new(highest_factory, version_alternates) end private - def factory_at_version(version, logging_interceptor) + def factory_at_version(version, error_interceptor, logging_interceptor) factory = Factories.at_version(version).services.new(**{ - service_path: service_path, logging_interceptor: logging_interceptor, + error_interceptor: error_interceptor, + deprecation: deprecator }.merge(gax_service_params)) factory @@ -54,7 +57,7 @@ def gax_service_params { credentials: credentials_or_channel, metadata: headers, - exception_transformer: GoogleAds::ERROR_TRANSFORMER + endpoint: endpoint } end @@ -90,11 +93,12 @@ def validate_login_customer_id attr_reader :name attr_reader :version - attr_reader :service_path attr_reader :logger attr_reader :config attr_reader :credentials_or_channel attr_reader :lookup_util + attr_reader :endpoint + attr_reader :deprecator end end end diff --git a/lib/google/ads/google_ads/service_wrapper.rb b/lib/google/ads/google_ads/service_wrapper.rb new file mode 100644 index 000000000..e52b6dba2 --- /dev/null +++ b/lib/google/ads/google_ads/service_wrapper.rb @@ -0,0 +1,138 @@ +require 'google/ads/google_ads/utils/string_utils' + +module Google + module Ads + module GoogleAds + class ServiceWrapper + # @param service a generated gax client object + # @param rpc_inputs [Hash] a hash from rpc method names on the passed + # service to their input types, e.g. for GoogleAdsService::Client a + # hash containing {search: SearchGoogleAdsRequest} etc. + # + def initialize(service:, rpc_inputs:, deprecation:) + @service = service + @rpc_inputs = rpc_inputs + @deprecation = deprecation + end + + def configure(&block) + @service.configure(&block) + end + + def respond_to_missing?(m, include_private=false) + service.respond_to?(m, include_private) + end + + def method_missing(name, *args, **kwargs, &blk) + # delegate with no request manipulation if the method isn't an + # rpc, so that we don't elide any of the configuration or other + # methods available on the gax clients + unless rpc_inputs.include?(name) + return service.public_send(name, *args, **kwargs, &blk) + end + + rpc_input_class = rpc_inputs.fetch(name) + request = rpc_input_class.new + + if kwargs.include?(:request) || kwargs.include?(:options) + options = kwargs.delete(:options) + + kwargs.fetch(:request, {}).each do |name, value| + write_field(request, value, name) + end + + service.public_send(name, request, options) + elsif !kwargs.empty? && args.empty? + kwargs.each do |name, value| + write_field(request, value, name) + end + + service.public_send(name, request, nil) + elsif args.empty? + # no args specified at all, just pass through + service.public_send(name, request, nil) + else + # this branch is the legacy version + kwargs.each do |name, value| + request.public_send("#{name}=", value) + end + + # zip will fill with nils by default, so we truncate the number + # of fields we set to match the argument length here. + args_and_fields = args.zip( + request_positional_args(rpc_input_class)[0...args.length] + ) + + field_names = [] + args_and_fields.each do |(arg, field)| + write_field(request, arg, field) + field_names << field.name + end + + service_name_segments = service.class.name.split("::") + service_name = GoogleAds::Utils.underscore( + service_name_segments[service_name_segments.length-2] + ) + method_name = "#{service_name}.#{name}" + deprecation.deprecate( + "Calling #{method_name} with positional " \ + "arguments is deprecated, please update it to use kwargs, e.g.: " \ + "#{method_name}" \ + "(#{field_names.map { |fn| "#{fn}: ..."}.join(", ")})" + ) + + service.public_send(name, request) + end + end + + private + + def request_positional_args(rpc_input_class) + # one of fields are never positional arguments in the old monolithic + # generator's code, so this selects all the one of fields in the + # descriptor and puts their names in a set. Then we remove them + # from the final array of args we return. + one_of_fields = Set.new + rpc_input_class.descriptor.each_oneof do |one_of| + one_of.each do |field| + one_of_fields.add(field.name) + end + end + + fields_to_reject = one_of_fields + fields_to_reject.add("partial_failure") + fields_to_reject.add("validate_only") + + rpc_input_class.descriptor.to_a.reject { |field| + fields_to_reject.include?(field.name) + } + end + + def write_field(request, arg, field) + if Symbol === field + name = field.to_s + field = request.class.descriptor.lookup(field.to_s) + raise ArgumentError, "unknown field #{name}" if field.nil? + end + + if field.label == :repeated + unless Array === arg + raise( + ArgumentError, + "Must pass an array to assign to repeated field #{field.name}", + ) + end + repeated = request.public_send(field.name) + arg.each { |a| repeated << a } + else + request.public_send("#{field.name}=", arg) + end + end + + attr_reader :service + attr_reader :rpc_inputs + attr_reader :deprecation + end + end + end +end diff --git a/lib/google/ads/google_ads/utils/build_path_lookup_class.rb b/lib/google/ads/google_ads/utils/build_path_lookup_class.rb new file mode 100644 index 000000000..0625289e9 --- /dev/null +++ b/lib/google/ads/google_ads/utils/build_path_lookup_class.rb @@ -0,0 +1,58 @@ +#!/usr/bin/ruby +# Encoding: utf-8 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Utility that generates up resource names for entities given IDs. + +require "google/ads/google_ads/utils/path_lookup_definer" +require "google/ads/google_ads/utils/path_lookup_config" + +module Google + module Ads + module GoogleAds + module Utils + def self.build_path_lookup_class(version) + Class.new do + define_method(:lookups) do + GoogleAds.const_get("Utils::PathLookupConfig::PATH_LOOKUP_#{version.upcase}") + end + + define_method(:respond_to_missing?) do |name, include_private=false| + lookups.include?(name) || super + end + + define_method(:method_missing) do |name, *args, **kwargs| + if !lookups.include?(name) + raise NoMethodError, "undefined method `#{name}' for #{inspect}" + end + + if args.any? { |arg| arg.nil? } + raise ArgumentError, "invalid args for #{name}: #{args.inspect}" + end + + define_lookup_method(name, version) + send(name, *args, **kwargs) + end + + define_method(:define_lookup_method) do |name, version| + Utils::PathLookupDefiner.new(self, name, lookups.fetch(name)).call(version) + end + end + end + end + end + end +end diff --git a/lib/google/ads/google_ads/utils/path_lookup_config.rb b/lib/google/ads/google_ads/utils/path_lookup_config.rb new file mode 100644 index 000000000..997ea7062 --- /dev/null +++ b/lib/google/ads/google_ads/utils/path_lookup_config.rb @@ -0,0 +1,156 @@ +module Google + module Ads + module GoogleAds + module Utils + module PathLookupConfig + # PATH_LOOKUP defines the compound resource name structure for + # each of our API calls, grouping by compound resource names. + # Consider the entry: + # + # ```ruby + # ad_group_ad_label: [:customer, [:ad_group, :ad, :label]]. + # ``` + # + # this means that a method should get defined on this object + # called ad_group_ad_label which has 4 keyword arguments: + # 1. customer + # 2. ad_group + # 3. ad + # 4. label + # + # these are grouped in to two path segments, customer, and + # [ad_group, ad, label]. The underlying gapic generated + # path helper will then be called with: + # + # ```ruby + # ad_group_ad_label( + # customer: , + # ad_group_ad_label: "~~