├── .rspec
├── lib
├── bing-ads-api
│ ├── version.rb
│ ├── data
│ │ ├── bid.rb
│ │ ├── predicate.rb
│ │ ├── city_target_bid.rb
│ │ ├── postal_code_target_bid.rb
│ │ ├── radius_target_bid.rb
│ │ ├── city_target.rb
│ │ ├── radius_target.rb
│ │ ├── postal_code_target.rb
│ │ ├── location_target.rb
│ │ ├── target.rb
│ │ ├── report_request_status.rb
│ │ ├── budget.rb
│ │ ├── keyword.rb
│ │ ├── campaign.rb
│ │ ├── reporting
│ │ │ ├── helpers
│ │ │ │ ├── scope_helper.rb
│ │ │ │ ├── column_helper.rb
│ │ │ │ ├── time_helper.rb
│ │ │ │ └── filter_helper.rb
│ │ │ ├── performance_report_request.rb
│ │ │ ├── share_of_voice_report_request.rb
│ │ │ ├── keyword_performance_report_request.rb
│ │ │ ├── account_performance_report_request.rb
│ │ │ ├── budget_summary_report_request.rb
│ │ │ └── campaign_performance_report_request.rb
│ │ ├── bulk
│ │ │ ├── bulk_download_status.rb
│ │ │ └── bulk_upload_status.rb
│ │ ├── accounts_info.rb
│ │ ├── report_request.rb
│ │ ├── ad_group.rb
│ │ └── ad.rb
│ ├── fault
│ │ ├── ad_api_error.rb
│ │ ├── application_fault.rb
│ │ ├── operation_error.rb
│ │ ├── batch_error.rb
│ │ ├── ad_api_fault_detail.rb
│ │ ├── partial_errors.rb
│ │ └── api_fault_detail.rb
│ ├── api_exception.rb
│ ├── data_object.rb
│ ├── config.rb
│ ├── service
│ │ ├── customer_management.rb
│ │ ├── customer_billing.rb
│ │ ├── reporting.rb
│ │ └── bulk.rb
│ ├── constants.rb
│ ├── client_proxy.rb
│ ├── soap_hasheable.rb
│ └── service.rb
├── tasks
│ └── bing-ads-api_tasks.rake
├── bing-ads-api.rb
└── locales
│ └── es.yml
├── .gitignore
├── .project
├── Gemfile
├── Rakefile
├── spec
├── customer_management_spec.rb
├── column_helper_spec.rb
├── report_request_spec.rb
├── performance_report_request_spec.rb
├── config_spec.rb
├── keyword_performance_report_request_spec.rb
├── spec_helper.rb
├── data_object_spec.rb
├── account_performance_report_request_spec.rb
├── campaign_performance_report_request_spec.rb
├── filter_helper_spec.rb
├── client_proxy_spec.rb
├── helpers
│ └── bing_ads_factory.rb
├── bulk_spec.rb
├── reporting_spec.rb
└── campaign_management_spec.rb
├── bing-ads-api.gemspec
├── MIT-LICENSE
└── README.rdoc
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format progress
3 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/version.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | module BingAdsApi
3 |
4 | # Gem Version
5 | VERSION = "0.7.1"
6 | end
7 |
--------------------------------------------------------------------------------
/lib/tasks/bing-ads-api_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :bing-ads-api do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | test/dummy/db/*.sqlite3
5 | test/dummy/log/*.log
6 | test/dummy/tmp/
7 | test/dummy/.sass-cache
8 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | bing-ads-api
4 |
5 |
6 |
7 |
8 |
9 |
10 | com.aptana.projects.webnature
11 | org.radrails.rails.core.railsnature
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/bid.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public: Define a bid
6 | #
7 | # Author:: alex.cavalli@offers.com
8 | #
9 | # Examples
10 | # bid = BingAdsApi::Bid.new(:amount => 1.23)
11 | # # =>
12 | class Bid < BingAdsApi::DataObject
13 |
14 | attr_accessor :amount
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/predicate.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | class Predicate < BingAdsApi::DataObject
6 |
7 | attr_accessor :field, :operator, :value
8 |
9 | def to_hash(keys = :underscore)
10 | hash = super(keys)
11 | return hash
12 | end
13 |
14 | private
15 |
16 | def get_key_order
17 | super.concat(BingAdsApi::Config.instance.
18 | customer_billing_orders['predicate'])
19 | end
20 |
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/ad_api_error.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines an error object that contains the details that explain why the service operation failed.
6 | #
7 | # Reference : http://msdn.microsoft.com/en-US/library/bing-ads-overview-adapierror.aspx
8 | #
9 | # Author:: jlopezn@neonline.cl
10 | class AdApiError < BingAdsApi::DataObject
11 |
12 | attr_accessor :code, :detail, :error_code, :message
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/application_fault.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines the base object from which all fault detail objects derive.
6 | #
7 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-overview-applicationfault.aspx
8 | #
9 | # Author:: jlopezn@neonline.cl
10 | class ApplicationFault < BingAdsApi::DataObject
11 |
12 | attr_accessor :tracking_id
13 |
14 | def to_s
15 | return tracking_id.to_s
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/city_target_bid.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public: Define a bid
6 | #
7 | # Author:: alex.cavalli@offers.com
8 | #
9 | # Examples
10 | # bid = BingAdsApi::Bid.new(:amount => 1.23)
11 | # # =>
12 | class CityTargetBid < BingAdsApi::DataObject
13 |
14 | attr_accessor :bid_adjustment,
15 | :city,
16 | :is_excluded
17 | end
18 |
19 | def to_hash(keys = :camelcase)
20 | hash = super(keys)
21 | return hash
22 | end
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/postal_code_target_bid.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public: Define a bid
6 | #
7 | # Author:: alex.cavalli@offers.com
8 | #
9 | # Examples
10 | # bid = BingAdsApi::Bid.new(:amount => 1.23)
11 | # # =>
12 | class PostalCodeTargetBid < BingAdsApi::DataObject
13 |
14 | attr_accessor :bid_adjustment,
15 | :postal_code,
16 | :is_excluded
17 | end
18 |
19 | def to_hash(keys = :camelcase)
20 | hash = super(keys)
21 | return hash
22 | end
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Declare your gem's dependencies in bing-ads-api.gemspec.
4 | # Bundler will treat runtime dependencies like base dependencies, and
5 | # development dependencies will be added by default to the :development group.
6 | gemspec
7 |
8 | # Declare any dependencies that are still in development here instead of in
9 | # your gemspec. These might include edge Rails or gems from your path or
10 | # Git. Remember to move these dependencies to your gemspec before releasing
11 | # your gem to rubygems.org.
12 |
13 | # To use debugger
14 | # gem 'ruby-debug19', :require => 'ruby-debug'
15 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/radius_target_bid.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public: Define a bid
6 | #
7 | # Author:: alex.cavalli@offers.com
8 | #
9 | # Examples
10 | # bid = BingAdsApi::Bid.new(:amount => 1.23)
11 | # # =>
12 | class RadiusTargetBid < BingAdsApi::DataObject
13 |
14 | attr_accessor :bid_adjustment,
15 | :latitude_degrees,
16 | :longitude_degrees,
17 | :radius,
18 | :radius_unit,
19 | :is_excluded
20 | end
21 |
22 | def to_hash(keys = :camelcase)
23 | hash = super(keys)
24 | return hash
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/operation_error.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines an error object that contains the details that explain why the service operation failed.
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | #
9 | # Reference : http://msdn.microsoft.com/en-US/library/bing-ads-overview-operationerror.aspx
10 | class OperationError < BingAdsApi::DataObject
11 |
12 | attr_accessor :code, :details, :error_code, :message
13 |
14 |
15 | # Public : Specified to string method
16 | #
17 | # Author:: jlopezn@neonline.cl
18 | def to_s
19 | "#{code}:#{error_code} - #{message}. #{details}"
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/city_target.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object of an ad.
7 | # Do not instantiate this object. Instead you can instantiate the
8 | # BingAdsApi::TextAd, BingAdsApi::MobileAd, or BingAdsApi::ProductAd
9 | # object that derives from this object.
10 | #
11 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-campaign-management-ad.aspx
12 | #
13 | # Author:: jlopezn@neonline.cl
14 | #
15 | class CityTarget < BingAdsApi::DataObject
16 |
17 | attr_accessor :bids
18 |
19 | def to_hash(keys = :camelcase)
20 | hash = super(keys)
21 | return hash
22 | end
23 |
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/radius_target.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object of an ad.
7 | # Do not instantiate this object. Instead you can instantiate the
8 | # BingAdsApi::TextAd, BingAdsApi::MobileAd, or BingAdsApi::ProductAd
9 | # object that derives from this object.
10 | #
11 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-campaign-management-ad.aspx
12 | #
13 | # Author:: jlopezn@neonline.cl
14 | #
15 | class RadiusTarget < BingAdsApi::DataObject
16 |
17 | attr_accessor :bids
18 |
19 | def to_hash(keys = :camelcase)
20 | hash = super(keys)
21 | return hash
22 | end
23 |
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/postal_code_target.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object of an ad.
7 | # Do not instantiate this object. Instead you can instantiate the
8 | # BingAdsApi::TextAd, BingAdsApi::MobileAd, or BingAdsApi::ProductAd
9 | # object that derives from this object.
10 | #
11 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-campaign-management-ad.aspx
12 | #
13 | # Author:: jlopezn@neonline.cl
14 | #
15 | class PostalCodeTarget < BingAdsApi::DataObject
16 |
17 | attr_accessor :bids
18 |
19 | def to_hash(keys = :camelcase)
20 | hash = super(keys)
21 | return hash
22 | end
23 |
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/location_target.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object of an location target.
7 | #
8 | # Reference: https://msdn.microsoft.com/library/4349d964-0553-4d68-a53e-5011ff51a8f9
9 | #
10 | # Author:: seodma@gmailc.com
11 | #
12 | class LocationTarget < BingAdsApi::DataObject
13 |
14 | attr_accessor :id,
15 | :city_target,
16 | :radius_target,
17 | :postal_code_target
18 |
19 | def to_hash(keys = :camelcase)
20 | hash = super(keys)
21 | return hash
22 | end
23 |
24 | private
25 |
26 | # Internal: Retrieve the ordered array of keys corresponding to this data
27 | # object.
28 | #
29 | # Author: alex.cavalli@offers.com
30 |
31 | end
32 |
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | begin
3 | require 'bundler/setup'
4 | rescue LoadError
5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6 | end
7 | begin
8 | require 'rdoc/task'
9 | rescue LoadError
10 | require 'rdoc/rdoc'
11 | require 'rake/rdoctask'
12 | RDoc::Task = Rake::RDocTask
13 | end
14 |
15 | RDoc::Task.new(:rdoc) do |rdoc|
16 | rdoc.rdoc_dir = 'rdoc'
17 | rdoc.title = 'BingAdsApi'
18 | rdoc.options << '--line-numbers'
19 | rdoc.rdoc_files.include('README.rdoc')
20 | rdoc.rdoc_files.include('lib/**/*.rb')
21 | end
22 |
23 | Bundler::GemHelper.install_tasks
24 |
25 | require 'rake/testtask'
26 |
27 | Rake::TestTask.new(:test) do |t|
28 | t.libs << 'lib'
29 | t.libs << 'test'
30 | t.pattern = 'test/**/*_test.rb'
31 | t.verbose = false
32 | end
33 |
34 |
35 | task :default => :test
36 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/target.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object of an location target.
7 | #
8 | # Reference: https://msdn.microsoft.com/library/4349d964-0553-4d68-a53e-5011ff51a8f9
9 | #
10 | # Author:: seodma@gmailc.com
11 | #
12 | class Target < BingAdsApi::DataObject
13 |
14 | attr_accessor :id,
15 | :location
16 |
17 |
18 | # Internal: Retrieve the ordered array of keys corresponding to this data
19 | # object.
20 | #
21 | # Author: alex.cavalli@offers.com
22 | def to_hash(keys = :camelcase)
23 | hash = super(keys)
24 | return hash
25 | end
26 |
27 | private
28 |
29 | # def get_key_order
30 | # super.concat(BingAdsApi::Config.instance.
31 | # campaign_management_orders['target'])
32 | # end
33 |
34 |
35 | end
36 |
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/spec/customer_management_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 |
4 | # Author:: jlopezn@neonline.cl
5 | describe BingAdsApi::CustomerManagement do
6 |
7 | let(:default_options) do
8 | {
9 | environment: :sandbox,
10 | username: "ruby_bing_ads_sbx",
11 | password: "sandbox123",
12 | developer_token: "BBD37VB98",
13 | customer_id: "21025739",
14 | account_id: "8506945"
15 | }
16 | end
17 | let(:service) { BingAdsApi::CustomerManagement.new(default_options) }
18 |
19 | it "should initialize with options" do
20 | new_service = BingAdsApi::CustomerManagement.new(default_options)
21 | expect(new_service).not_to be_nil
22 | end
23 |
24 | it "should get accounts info" do
25 | response = service.get_accounts_info
26 | expect(response).not_to be_nil
27 | expect(response).to be_kind_of(Array)
28 | end
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/bing-ads-api.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "bing-ads-api/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "bing-ads-api"
9 | s.version = BingAdsApi::VERSION
10 | s.authors = ["Juan Pablo Lopez N", "Alex Cavalli", "Colin Knox"]
11 | s.email = ["alex.cavalli@offers.com"]
12 | s.homepage = "https://github.com/alexcavalli/bing-ads-api"
13 | s.summary = "Bing Ads API for Ruby"
14 | s.description = "A set of pure Ruby classes for the Bing Ads API"
15 |
16 | s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
17 | s.test_files = Dir["spec/**/*"]
18 |
19 | s.add_dependency "savon", "~> 2.5"
20 | s.add_dependency "rest-client"
21 | s.add_dependency "activesupport"
22 |
23 | s.add_development_dependency "rspec"
24 | end
25 |
--------------------------------------------------------------------------------
/spec/column_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::Helpers::ColumnHelper do
4 | include BingAdsApi::Helpers::ColumnHelper
5 |
6 | it "should return true if all the columns are found in the valid column list" do
7 | expect{
8 | valid_columns(
9 | {account_name: "AccountName"},
10 | [:account_name, "AccountName"]
11 | )
12 | }.to raise_error
13 | end
14 |
15 | it "should raise column exception when symbol is not in valid column keys" do
16 | expect{
17 | valid_columns(
18 | {account_name: "UnknownColumn"},
19 | [:account_name, :unknown_column]
20 | )
21 | }.to raise_error
22 | end
23 |
24 | it "should raise column exception when string is not in valid column values" do
25 | expect{
26 | valid_columns(
27 | {unknown_column: "Acct"},
28 | [:account_name, "UnknownColumn" ],
29 | )
30 | }.to raise_error
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/report_request_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 |
4 | # Author:: jlopezn@neonline.cl
5 | describe BingAdsApi::ReportRequest do
6 | it "should initialize when valid parameters are provided" do
7 | expect{
8 | report_request = BingAdsApi::ReportRequest.new(:format => :xml,
9 | :language => :english, :report_name => "My Report",
10 | :return_only_complete_data => true)
11 | }.not_to raise_error
12 | end
13 |
14 | it "should raise an exception when an invalid format is provided" do
15 | expect{
16 | report_request = BingAdsApi::ReportRequest.new(:format => :invalid,
17 | :language => :english, :report_name => "My Report",
18 | :return_only_complete_data => true)
19 | }.to raise_error
20 | end
21 |
22 | it "should raise an exception when an invalid language is provided" do
23 | expect{
24 | report_request = BingAdsApi::ReportRequest.new(:format => :xml,
25 | :language => :swahili, :report_name => "My Report",
26 | :return_only_complete_data => true)
27 | }.to raise_error
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2013 YOURNAME
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/spec/performance_report_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::PerformanceReportRequest do
4 | it "should initialize when valid parameters are provided" do
5 | expect{
6 | performance_report_request = BingAdsApi::PerformanceReportRequest.new(:format => :xml,
7 | :language => :english, :report_name => "My Report",
8 | :aggregation => :hourly, :time => :today)
9 | }.not_to raise_error
10 | end
11 |
12 | it "should raise an exception when an invalid aggregation is provided" do
13 | expect{
14 | performance_report_request = BingAdsApi::PerformanceReportRequest.new(:format => :xml,
15 | :language => :english, :report_name => "My Report",
16 | :aggregation => :invalid_aggregation)
17 | }.to raise_error
18 | end
19 |
20 | it "should raise an exception when an invalid time is provided" do
21 | expect{
22 | performance_report_request = BingAdsApi::PerformanceReportRequest.new(:format => :xml,
23 | :language => :english, :report_name => "My Report",
24 | :aggregation => :hourly, :time => :invalid_time)
25 | }.to raise_error
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/api_exception.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Generic exception thrown by service classes in BingAdsApi.
6 | # Exception of this kind wrap an AdApiFaultDetail or ApiFaultDetail instance
7 | # to look over the specific details of the SOAP request fault.
8 | #
9 | # Author:: jlopezn@neonline.cl
10 | #
11 | # Example
12 | #
13 | #
14 | #
15 | #
16 | class ApiException < Exception
17 |
18 | attr_accessor :fault_object
19 |
20 | # Public : Constructor. Based on the default Exception constructor,
21 | # adds the fault_object instance, that can be an ApiFaultDetail or
22 | # AdApiFaultDetail instance
23 | #
24 | # === Parameters
25 | # * fault_object - AdApiFaultDetail or ApiFaultDetail instance
26 | # * msg - optional message
27 | #
28 | # Author:: jlopezn@neonline.cl
29 | def initialize(fault_object, msg=nil)
30 | super(msg)
31 | self.fault_object = fault_object
32 | end
33 |
34 |
35 | # Public : Specified to string method
36 | #
37 | # Author:: jlopezn@neonline.cl
38 | def to_s
39 | super.to_s + " - " + fault_object.to_s
40 | end
41 | end
42 | end
--------------------------------------------------------------------------------
/spec/config_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::Config do
4 |
5 | let(:config) { BingAdsApi::Config.instance }
6 |
7 | it "should load" do
8 | expect(config).not_to be_nil
9 | expect(config.config).not_to be_nil
10 | end
11 |
12 | it "should load config constants" do
13 | expect(config.common_constants).not_to be_nil
14 | expect(config.campaign_management_constants).not_to be_nil
15 | expect(config.reporting_constants).not_to be_nil
16 | end
17 |
18 | it "should load config common constants" do
19 | expect(config.common_constants['time_zones']).not_to be_nil
20 | expect(config.common_constants['time_zones']['santiago']).not_to be_nil
21 | end
22 |
23 | it "should load config campaign management orders" do
24 | expect(config.campaign_management_orders['ad']).not_to be_nil
25 | end
26 |
27 | it "should get sandbox wsdl" do
28 | expect(config.service_wsdl(:sandbox, :campaign_management)).not_to be_nil
29 | expect(config.service_wsdl(:sandbox, :reporting)).not_to be_nil
30 | end
31 |
32 | it "should get production wsdl" do
33 | expect(config.service_wsdl(:production, :campaign_management)).not_to be_nil
34 | expect(config.service_wsdl(:production, :reporting)).not_to be_nil
35 | end
36 |
37 | end
38 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/report_request_status.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the status of a report request.
7 | #
8 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-reporting-bing-ads-reportrequeststatus.aspx
9 | #
10 | # Author:: jlopezn@neonline.cl
11 | class ReportRequestStatus < BingAdsApi::DataObject
12 |
13 | # Valid report request status for reports
14 | REQUEST_STATUS = BingAdsApi::Config.instance.
15 | reporting_constants['request_status_type']
16 |
17 | attr_accessor :report_download_url, :status
18 |
19 | # Public:: Returns true if the status is success
20 | #
21 | # Author:: jlopezn@neonline.cl
22 | #
23 | # Returns:: boolean
24 | def success?
25 | return status == REQUEST_STATUS['success']
26 | end
27 |
28 | # Public:: Returns true if the status is pending
29 | #
30 | # Author:: jlopezn@neonline.cl
31 | #
32 | # Returns:: boolean
33 | def pending?
34 | return status == REQUEST_STATUS['pending']
35 | end
36 |
37 | # Public:: Returns true if the status is error
38 | #
39 | # Author:: jlopezn@neonline.cl
40 | #
41 | # Returns:: boolean
42 | def error?
43 | return status == REQUEST_STATUS['error']
44 | end
45 |
46 | end
47 |
48 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/budget.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public: Define a keyword
6 | #
7 | # Author:: alex.cavalli@offers.com
8 | #
9 | # Examples
10 | # keyword = BingAdsApi::Keyword.new(
11 | # :bid => BingAdsApi::Bid.new(:amount => 1.23),
12 | # :destination_url => "http://www.example.com/",
13 | # :id => 123456789,
14 | # :match_type => BingAdsApi::Keyword::EXACT,
15 | # :param1 => "param1",
16 | # :param2 => "param2",
17 | # :param3 => "param3",
18 | # :status => BingAdsApi::Keyword::ACTIVE,
19 | # :text => "keyword text")
20 | # # =>
21 | class Budget < BingAdsApi::DataObject
22 |
23 | attr_accessor :amount,
24 | :association_count,
25 | :budget_type,
26 | :id,
27 | :name
28 |
29 | def to_hash(keys = :underscore)
30 | hash = super(keys)
31 | hash[:'@xsi:type'] = "#{ClientProxy::NAMESPACE}:Budget"
32 | return hash
33 | end
34 |
35 | private
36 |
37 | # Internal: Retrieve the ordered array of keys corresponding to this data
38 | # object.
39 | #
40 | # Author: alex.cavalli@offers.com
41 | def get_key_order
42 | super.concat(BingAdsApi::Config.instance.
43 | campaign_management_orders['budget'])
44 | end
45 |
46 | end
47 |
48 | end
49 |
--------------------------------------------------------------------------------
/spec/keyword_performance_report_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::KeywordPerformanceReportRequest do
4 | it "should initialize campaign performance report request" do
5 | expect{
6 | request = BingAdsApi::KeywordPerformanceReportRequest.new(
7 | :format => :xml,
8 | :language => :english,
9 | :report_name => "Me report",
10 | :aggregation => :hourly,
11 | :columns => [:account_name, :account_number, :time_period],
12 | # The filter is specified as a hash
13 | :filter => {
14 | # specifies the Bing expected String value
15 | :ad_distribution => "Search",
16 | :ad_type => "Text",
17 | :bid_match_type => "Exact",
18 | :delivered_match_type => "Exact",
19 | # specifies criteria as a snake case symbol
20 | :device_type => :tablet,
21 | :keyword_relevance => [3],
22 | :keywords => ["bing ads"],
23 | :landing_page_relevance => [2],
24 | :landing_page_user_experience => [2],
25 | :language_code => ["EN"],
26 | :quality_score => [7,8,9,10] },
27 | :max_rows => 10,
28 | :scope => {
29 | :account_ids => [123456, 234567] },
30 | # predefined date
31 | :time => :this_week)
32 | }.not_to raise_error
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/keyword.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public: Define a keyword
6 | #
7 | # Author:: alex.cavalli@offers.com
8 | #
9 | # Examples
10 | # keyword = BingAdsApi::Keyword.new(
11 | # :bid => BingAdsApi::Bid.new(:amount => 1.23),
12 | # :destination_url => "http://www.example.com/",
13 | # :id => 123456789,
14 | # :match_type => BingAdsApi::Keyword::EXACT,
15 | # :param1 => "param1",
16 | # :param2 => "param2",
17 | # :param3 => "param3",
18 | # :status => BingAdsApi::Keyword::ACTIVE,
19 | # :text => "keyword text")
20 | # # =>
21 | class Keyword < BingAdsApi::DataObject
22 | include BingAdsApi::KeywordEditorialStatuses
23 | include BingAdsApi::KeywordStatuses
24 | include BingAdsApi::MatchTypes
25 |
26 |
27 | attr_accessor :bid,
28 | :destination_url,
29 | :editorial_status,
30 | :forward_compatibility_map,
31 | :id,
32 | :match_type,
33 | :param1,
34 | :param2,
35 | :param3,
36 | :status,
37 | :text
38 |
39 | private
40 |
41 | # Internal: Retrieve the ordered array of keys corresponding to this data
42 | # object.
43 | #
44 | # Author: alex.cavalli@offers.com
45 | def get_key_order
46 | super.concat(BingAdsApi::Config.instance.
47 | campaign_management_orders['keyword'])
48 | end
49 |
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file was generated by the `rspec --init` command. Conventionally, all
2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3 | # Require this file using `require "spec_helper"` to ensure that it is only
4 | # loaded once.
5 | #
6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7 |
8 | require 'bing-ads-api'
9 | require 'helpers/bing_ads_factory'
10 |
11 | RSpec.configure do |config|
12 | config.run_all_when_everything_filtered = true
13 | config.filter_run :focus
14 |
15 | # Run specs in random order to surface order dependencies. If you find an
16 | # order dependency and want to debug it, you can fix the order by providing
17 | # the seed, which is printed after each run.
18 | # --seed 1234
19 | config.order = 'random'
20 |
21 | config.after(:all) do
22 | # Clean up everything
23 | account_id = "242492622"
24 | service = BingAdsApi::CampaignManagement.new(
25 | environment: :sandbox,
26 | username: "dmitriiwebstreak1",
27 | password: "webstreak123",
28 | developer_token: "BBD37VB98",
29 | customer_id: "254553215",
30 | account_id: account_id
31 | )
32 | campaign_ids = service.get_campaigns_by_account_id(account_id).map(&:id)
33 |
34 | campaign_ids.each_slice(100) do |ids|
35 | service.delete_campaigns(account_id, ids)
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/data_object_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 |
4 | describe BingAdsApi::DataObject do
5 | describe "#to_hash" do
6 |
7 | # TODO: Fix this test to test a "TestDataObject" (the interface) instead of
8 | # the Campaign object implementation
9 | it "should return a hash of the object attributes" do
10 | camp = BingAdsApi::Campaign.new(
11 | :budget_type => "DailyBudgetStandard",
12 | :conversion_tracking_enabled => "false",
13 | :daily_budget => 2000,
14 | :daylight_saving => "false",
15 | :description => "Some Campaign",
16 | :monthly_budget => 5400,
17 | :name => "Some campaign",
18 | :status => "Paused",
19 | :time_zone => "Santiago",
20 | :campaign_type => "DynamicSearchAds")
21 |
22 | h = camp.to_hash
23 |
24 | expect(h).to be_kind_of(Hash)
25 | expect(h).not_to be_nil
26 |
27 | expect(h["budget_type"]).to eq(camp.budget_type)
28 | expect(h["conversion_tracking_enabled"]).to eq(camp.conversion_tracking_enabled)
29 | expect(h["daily_budget"]).to eq(camp.daily_budget)
30 | expect(h["daylight_saving"]).to eq(camp.daylight_saving)
31 | expect(h["description"]).to eq(camp.description)
32 | expect(h["monthly_budget"]).to eq(camp.monthly_budget)
33 | expect(h["name"]).to eq(camp.name)
34 | expect(h["status"]).to eq(camp.status)
35 | expect(h["time_zone"]).to eq(camp.time_zone)
36 | expect(h["campaign_type"]).to eq(camp.campaign_type)
37 | end
38 |
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/campaign.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Define a campaign
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | #
9 | # Examples
10 | # campaign = BingAdsApi::Campaign.new(
11 | # :budget_type => BingAdsApi::Campaign::DAILY_BUDGET_STANDARD,
12 | # :conversion_tracking_enabled => "false",
13 | # :daily_budget => 2000,
14 | # :daylight_saving => "false",
15 | # :description => name + " first description",
16 | # :monthly_budget => 5400,
17 | # :name => name + " first name",
18 | # :status => BingAdsApi::Campaign::PAUSED,
19 | # :time_zone => BingAdsApi::Campaign::SANTIAGO)
20 | # # =>
21 | class Campaign < BingAdsApi::DataObject
22 | include BingAdsApi::TimeZone
23 | include BingAdsApi::BudgetLimitType
24 | include BingAdsApi::CampaignStatus
25 | include BingAdsApi::PricingModel
26 |
27 |
28 | attr_accessor :id,
29 | :budget_type,
30 | :conversion_tracking_enabled,
31 | :daily_budget,
32 | :daylight_saving,
33 | :description,
34 | :monthly_budget,
35 | :name,
36 | :status,
37 | :time_zone,
38 | :campaign_type
39 |
40 | private
41 |
42 | # Internal: Retrieve the ordered array of keys corresponding to this data
43 | # object.
44 | #
45 | # Author: alex.cavalli@offers.com
46 | def get_key_order
47 | super.concat(BingAdsApi::Config.instance.
48 | campaign_management_orders['campaign'])
49 | end
50 |
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/account_performance_report_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::AccountPerformanceReportRequest do
4 | it "initialize account performance report request" do
5 | expect{
6 | BingAdsApi::AccountPerformanceReportRequest.new(
7 | :format => :xml, :language => :english, :report_name => "My Report",
8 | :aggregation => :hourly,
9 | :columns => [:account_name, :account_number, :time_period],
10 | :filter => {
11 | # String as bing expected
12 | :ad_distribution => "Search",
13 | # snake case symbol
14 | :device_os => :windows,
15 | # no specified value
16 | :device_type => nil
17 | },
18 | :scope => {:account_ids => 12345},
19 | :time => :today
20 | )
21 | }.not_to raise_error
22 | end
23 |
24 | it "should raise an exception if the scope is invalid" do
25 | expect{
26 | BingAdsApi::AccountPerformanceReportRequest.new(
27 | :format => :xml, :language => :english, :report_name => "My Report",
28 | :aggregation => :hourly,
29 | :columns => [:account_name, :account_number, :time_period],
30 | :filter => {
31 | # String as bing expected
32 | :ad_distribution => "Search",
33 | # snake case symbol
34 | :device_os => :windows,
35 | # no specified value
36 | :device_type => nil
37 | },
38 | :scope => {},
39 | :time => :today
40 | )
41 | }.to raise_error
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/helpers/scope_helper.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi::Helpers
4 |
5 | ##
6 | # Public : Utility module for scope attribute in +ReportRequest+ derived classes
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | module ScopeHelper
11 |
12 | include BingAdsApi::SOAPHasheable
13 |
14 |
15 | # Valid filters values for each known criteria
16 | FILTERS_CRITERIA = BingAdsApi::Config.instance.
17 | reporting_constants['filters']
18 |
19 |
20 | # Internal : Validates the filter attribute at the ReporRequest initialization
21 | # At the first invalid filter criteria or value detected this method raises Exception.
22 | # If all filter criteria and values are ok, this method returns true
23 | # Valid filter criteria are validated against FILTERS_CRITERIA constant
24 | # Valid filter values are validated against FILTERS constant
25 | #
26 | # Author:: jlopezn@neonline.cl
27 | #
28 | # === Parameters
29 | # valid_filters - Hash with the set of valid filter values
30 | # filter - Hash with the filter criteria and values
31 | #
32 | # Returns:: true if filter is valid
33 | #
34 | # Raises:: Exception at the first invalid filter criteria o value
35 | def valid_scope(valid_scopes, scope)
36 | end
37 |
38 |
39 | # Public : Returns the filter attribute as a Hash to SOAP requests
40 | #
41 | # Author:: jlopezn@neonline.cl
42 | #
43 | # filter - Hash with the filter values
44 | #
45 | # Returns:: Hash
46 | def scope_to_hash(keys_case=:undescore)
47 | end
48 |
49 |
50 | end
51 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/bulk/bulk_download_status.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public: Wrapper class for GetBulkDownloadStatusResponse. Not an
7 | # actual data object in the Bing API.
8 | #
9 | # http://msdn.microsoft.com/en-US/library/dn600289.aspx
10 | #
11 | # Author:: alex.cavalli@offers.com
12 | class BulkDownloadStatus < BingAdsApi::DataObject
13 |
14 | # Valid report request status for reports
15 | REQUEST_STATUS = BingAdsApi::Config.instance.
16 | bulk_constants['request_status_type']
17 |
18 | attr_accessor :errors, :forward_compatibility_map, :percent_complete,
19 | :request_status, :result_file_url
20 |
21 | # Public:: Returns true if the status is completed
22 | #
23 | # Author:: alex.cavalli@offers.com
24 | #
25 | # Returns:: boolean
26 | def completed?
27 | return request_status == REQUEST_STATUS['completed']
28 | end
29 |
30 | # Public:: Returns true if the request_status is in progress
31 | #
32 | # Author:: alex.cavalli@offers.com
33 | #
34 | # Returns:: boolean
35 | def in_progress?
36 | return request_status == REQUEST_STATUS['in_progress']
37 | end
38 |
39 | # Public:: Returns true if the status is failed
40 | #
41 | # Author:: alex.cavalli@offers.com
42 | #
43 | # Returns:: boolean
44 | def failed?
45 | return request_status == REQUEST_STATUS['failed']
46 | end
47 |
48 | # Public:: Returns true if the status is failed full sync required
49 | #
50 | # Author:: alex.cavalli@offers.com
51 | #
52 | # Returns:: boolean
53 | def failed_full_sync_required?
54 | return request_status == REQUEST_STATUS['failed_full_sync_required']
55 | end
56 |
57 | end
58 |
59 | end
60 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/batch_error.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines an error object that identifies the item within the batch of items
6 | # in the request message that caused the operation to fail, and describes the reason for the failure.
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | # Reference : http://msdn.microsoft.com/en-US/library/bing-ads-overview-batcherror.aspx
11 | class BatchError < BingAdsApi::DataObject
12 |
13 | attr_accessor :code, :details, :error_code, :index, :message, :type
14 |
15 | # Public : Specified to string method
16 | #
17 | # Author:: jlopezn@neonline.cl
18 | def to_s
19 | "#{index}:#{code}:#{error_code} - #{message} (#{type})"
20 | end
21 | end
22 |
23 | # Public : Subclass of BatchError present in AdAdds operations
24 | # Defines an error object that identifies the entity with the
25 | # batch of entities that failed editorial review.
26 | #
27 | # Reference : http://msdn.microsoft.com/en-US/library/bing-ads-overview-editorialerror.aspx
28 | #
29 | # Author:: jlopezn@neonline.cl
30 | class EditorialError < BingAdsApi::BatchError
31 |
32 | attr_accessor :appaleable, :disapproved_text, :location, :publisher_country, :reason_code
33 |
34 | # Public : Specified to string method
35 | #
36 | # Author:: jlopezn@neonline.cl
37 | def to_s
38 | str = super() + "\n"
39 | str += "\tAppaleable? #{appaleable}\n" if appaleable
40 | str += "\tDisapproved text: #{disapproved_text}\n" if disapproved_text
41 | str += "\tLocation: #{location}\n" if location
42 | str += "\tDisapproved text: #{disapproved_text}\n" if disapproved_text
43 | str += "\tReason code: #{reason_code} (see: http://msdn.microsoft.com/en-US/library/bing-ads-editorialfailurereasoncodes.aspx)\n" if reason_code
44 |
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/accounts_info.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Define an account info
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | #
9 | # Examples
10 | # campaign = BingAdsApi::AccountInfo.new(
11 | # :account_life_cycle_status => BingAdsApi::AccountsInfo::DRAFT
12 | # :name => "Account Name",
13 | # :number => 1234567,
14 | # :pause_reason => "1")
15 | # # =>
16 | class AccountInfo < BingAdsApi::DataObject
17 | include BingAdsApi::AccountLifeCycleStatuses
18 |
19 | attr_accessor :id, :account_life_cycle_status, :name, :number, :pause_reason
20 |
21 | # Public:: Returns true if the account is in status active
22 | #
23 | # Author:: jlopezn@neonline.cl
24 | #
25 | # Returns:: boolean
26 | def active?
27 | return account_life_cycle_status == ACTIVE
28 | end
29 |
30 |
31 | # Public:: Returns true if the account is in status draft
32 | #
33 | # Author:: jlopezn@neonline.cl
34 | #
35 | # Returns:: boolean
36 | def draft?
37 | return account_life_cycle_status == DRAFT
38 | end
39 |
40 |
41 | # Public:: Returns true if the account is in status inactive
42 | #
43 | # Author:: jlopezn@neonline.cl
44 | #
45 | # Returns:: boolean
46 | def inactive?
47 | return account_life_cycle_status == INACTIVE
48 | end
49 |
50 |
51 | # Public:: Returns true if the account is in status pause
52 | #
53 | # Author:: jlopezn@neonline.cl
54 | #
55 | # Returns:: boolean
56 | def pause?
57 | return account_life_cycle_status == PAUSE
58 | end
59 |
60 |
61 | # Public:: Returns true if the account is in status pending
62 | #
63 | # Author:: jlopezn@neonline.cl
64 | #
65 | # Returns:: boolean
66 | def pending?
67 | return account_life_cycle_status == PENDING
68 | end
69 |
70 | end
71 |
72 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/ad_api_fault_detail.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines a fault object that operations return when generic errors occur,
6 | # such as an authentication error.
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | class AdApiFaultDetail < BingAdsApi::ApplicationFault
10 |
11 | attr_accessor :errors
12 |
13 |
14 | # Public : Constructor
15 | #
16 | # Author:: jlopezn@neonline.cl
17 | #
18 | # attributes - Hash with the initial attributes
19 | # === Attributes
20 | # * tracking_id : the operation tracking id value
21 | # * errors : array of hashes with the Ad api errors
22 | def initialize(attributes={})
23 | super(attributes)
24 | if attributes
25 | initialize_errors(attributes[:errors])
26 | end
27 | end
28 |
29 | # Public : Specified to string method
30 | #
31 | # Author:: jlopezn@neonline.cl
32 | def to_s
33 | str = super.to_s + ":\n"
34 | if errors
35 | str += "\tErrors:\n" + errors.map{ |e| "\t" + e.to_s }.join("\n")
36 | end
37 |
38 | return str
39 | end
40 |
41 |
42 | private
43 |
44 | # Public : Helper method for the AdApiFaultDetail constructor
45 | # to initialize the errors array
46 | #
47 | # Author:: jlopezn@neonline.cl
48 | #
49 | # errors_hash - Hash with the :errors key received from the SOAP request
50 | #
51 | # Returns:: none
52 | def initialize_errors(errors_hash)
53 | return if errors_hash.nil?
54 |
55 | if errors_hash[:ad_api_error].is_a?(Array)
56 | self.errors = []
57 | errors_hash[:ad_api_error].each do |aae|
58 | self.errors << BingAdsApi::AdApiError.new(aae)
59 | end
60 | elsif errors_hash[:ad_api_error].is_a?(Hash)
61 | self.errors = [BingAdsApi::AdApiError.new(errors_hash[:ad_api_error])]
62 | end
63 | end
64 |
65 | end
66 |
67 | end
--------------------------------------------------------------------------------
/spec/campaign_performance_report_request_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::CampaignPerformanceReportRequest do
4 | it "should initialize campaign performance report request" do
5 | expect{
6 | performance_report_request = BingAdsApi::CampaignPerformanceReportRequest.new(:format => :xml,
7 | :language => :english, :report_name => "My Report",
8 | :aggregation => :hourly, :time => :today,
9 | :columns => [:account_name, :account_number, :time_period],
10 | :filter => {
11 | # String as bing expected
12 | :ad_distribution => "Search",
13 | :device_os => "Windows",
14 | # snake case symbol
15 | :device_type => :computer,
16 | # nil criteria
17 | :status => nil
18 | },
19 | :scope => {
20 | :account_ids => 12345,
21 | :campaigns => [
22 | {:account_id => 1234, :campaign_id => 1234},
23 | {:account_id => 1234, :campaign_id => 1234},
24 | {:account_id => 1234, :campaign_id => 1234}
25 | ]
26 | }
27 | )
28 | }.not_to raise_error
29 | end
30 |
31 | it "should raise an exception if the scope is invalid" do
32 | expect{
33 | performance_report_request = BingAdsApi::CampaignPerformanceReportRequest.new(:format => :xml,
34 | :language => :english, :report_name => "My Report",
35 | :aggregation => :hourly, :time => :today,
36 | :columns => [:account_name, :account_number, :time_period ],
37 | :scope => { :campaigns => []})
38 | }.to raise_error
39 |
40 | expect{
41 | performance_report_request = BingAdsApi::CampaignPerformanceReportRequest.new(:format => :xml,
42 | :language => :english, :report_name => "My Report",
43 | :aggregation => :hourly, :time => :today,
44 | :columns => [:account_name, :account_number, :time_period ],
45 | :scope => {:account_ids => 12345 })
46 | }.to raise_error
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/filter_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe BingAdsApi::Helpers::FilterHelper do
4 | include BingAdsApi::Helpers::FilterHelper
5 |
6 | let(:permitted_filters) {
7 | {
8 | "ad_distribution" => "AdDistribution",
9 | "device_os" => "DeviceOS",
10 | "device_type" => "DeviceType",
11 | "status" => "Status"
12 | }
13 | }
14 |
15 | it "should return true for valid filters" do
16 | expect(
17 | valid_filter(permitted_filters,
18 | {
19 | # String as bing expected
20 | :ad_distribution => "Search",
21 | :device_os => "Windows",
22 | # snake case symbol
23 | :device_type => :computer,
24 | # nil criteria
25 | :status => nil
26 | }
27 | )
28 | ).to eq(true)
29 | end
30 |
31 | it "should raise exception when invalid filter is provided" do
32 | expect{
33 | valid_filter(permitted_filters,
34 | {
35 | # Wrong String as bing expected
36 | :ad_distribution => "Searched",
37 | :device_os => "Windows",
38 | # snake case symbol
39 | :device_type => :computer,
40 | # nil criteria
41 | :status => nil
42 | }
43 | )
44 | }.to raise_error
45 |
46 | expect{
47 | valid_filter(permitted_filters,
48 | {
49 | # Wrong String as bing expected
50 | :ad_distribution => "Search",
51 | :device_os => "Windows",
52 | # Wrong snake case symbol
53 | :device_type => :notebook,
54 | # nil criteria
55 | :status => nil
56 | }
57 | )
58 | }.to raise_error
59 | end
60 |
61 | it "should raise exception when a bad key is provided" do
62 | expect{
63 | valid_filter(permitted_filters,
64 | {
65 | # Wrong filter criteria. ie: invalid key
66 | :not_a_valid_criteria => "Bleh",
67 | }
68 | )
69 | }.to raise_error
70 | end
71 |
72 | end
73 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/helpers/column_helper.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi::Helpers
4 |
5 | ##
6 | # Public : Utility module
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | module ColumnHelper
11 |
12 | # Internal : Validates the specified columns at the ReporRequest initialization
13 | # At the first invalid column detected this method raises Exception.
14 | # If all column values are ok, this method returns true
15 | # Valid columns are validated against COLUMNS constant
16 | #
17 | # Author:: jlopezn@neonline.cl
18 | #
19 | # valid_columns - Hash with the valid names and values for columns
20 | # columns - Hash with the columns specified in the initialization
21 | #
22 | # Returns:: true if all columns are ok
23 | # Raises:: Exception at the first invalid column detected
24 | def valid_columns(valid_columns, columns)
25 | columns.each do |col|
26 |
27 | if col.is_a?(String)
28 | if !valid_columns.value?(col)
29 | raise Exception.new("Invalid column value '#{col}'")
30 | end
31 | elsif col.is_a?(Symbol)
32 | if !valid_columns.key?(col.to_s)
33 | raise Exception.new("Invalid column name '#{col}'")
34 | end
35 | end
36 | end
37 | return true
38 | end
39 |
40 |
41 | # Public : Return the columns attribute of the ReportRequest as a valid Hash for SOAP requests
42 | #
43 | # Author:: jlopezn@neonline.cl
44 | #
45 | # keys_case - specifies the keys_case for the hash: :underscore or :camelcase
46 | #
47 | # Returns:: Hash
48 | def columns_to_hash(valid_columns, columns, keys_case=:underscore)
49 | raise Exception.new("Invalid time value: nil") if columns.nil?
50 |
51 | key = self.class.to_s.demodulize.gsub(/ReportRequest/, 'ReportColumn')
52 | return { key =>
53 | columns.map do |col|
54 | if col.is_a?(String)
55 | col
56 | elsif col.is_a?(Symbol)
57 | valid_columns[col.to_s]
58 | end
59 | end
60 | }
61 | end
62 |
63 |
64 | end
65 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | require 'savon'
4 | require 'bing-ads-api/api_exception'
5 | require 'bing-ads-api/config'
6 | require 'bing-ads-api/constants'
7 | require 'bing-ads-api/service'
8 | require 'bing-ads-api/client_proxy'
9 | require 'bing-ads-api/soap_hasheable'
10 | require 'bing-ads-api/data_object'
11 |
12 |
13 | # Require services
14 | Dir[File.join(File.dirname(__FILE__), 'bing-ads-api', 'service', '*.rb')].each { |file| require file }
15 |
16 | # Require data objects
17 | Dir[File.join(File.dirname(__FILE__), 'bing-ads-api', 'data', '*.rb')].each { |file| require file }
18 |
19 | # Require bulk data objects
20 | Dir[File.join(File.dirname(__FILE__), 'bing-ads-api', 'data', 'bulk', '*.rb')].each { |file| require file }
21 |
22 |
23 | # Require Fault objects
24 | require 'bing-ads-api/fault/application_fault'
25 | require 'bing-ads-api/fault/ad_api_error'
26 | require 'bing-ads-api/fault/ad_api_fault_detail'
27 | require 'bing-ads-api/fault/api_fault_detail'
28 | require 'bing-ads-api/fault/batch_error'
29 | require 'bing-ads-api/fault/operation_error'
30 | require 'bing-ads-api/fault/partial_errors'
31 |
32 | # Require Reporting helper objects
33 | Dir[File.join(File.dirname(__FILE__), 'bing-ads-api', 'data', 'reporting', 'helpers', '*.rb')].each { |file| require file }
34 |
35 | # Require report request data objects
36 | require 'bing-ads-api/data/reporting/performance_report_request'
37 | require 'bing-ads-api/data/reporting/account_performance_report_request'
38 | require 'bing-ads-api/data/reporting/campaign_performance_report_request'
39 | require 'bing-ads-api/data/reporting/keyword_performance_report_request'
40 | require 'bing-ads-api/data/reporting/share_of_voice_report_request'
41 | require 'bing-ads-api/data/reporting/budget_summary_report_request'
42 | # require 'bing-ads-api/data/reporting/ad_group_performance_report_request'
43 | # require 'bing-ads-api/data/reporting/ad_performance_report_request'
44 |
45 | # Public : This is the main namespace for all classes and submodules in this BingAdsApi Gem
46 | #
47 | # Author:: jlopezn@neonline.cl
48 | module BingAdsApi
49 |
50 |
51 | end
52 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data_object.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Base Class to define Bing API data objects
7 | # Do not use this class directly, use any of the derived classes
8 | #
9 | # Author:: jlopezn@neonline.cl
10 | #
11 | class DataObject
12 |
13 |
14 | include BingAdsApi::SOAPHasheable
15 |
16 | # Public : Constructor in a ActiveRecord style, with a hash of attributes as input
17 | #
18 | # Author:: jlopezn@neonline.cl
19 | #
20 | # attributes - Hash with the objects attributes
21 | def initialize(attributes={})
22 | attributes.each { |key, val| send("#{key}=", val) if respond_to?("#{key}=") }
23 | end
24 |
25 |
26 | # Public : Specified to string method
27 | #
28 | # Author:: jlopezn@neonline.cl
29 | def to_s
30 | to_hash.to_s
31 | end
32 |
33 |
34 | # Public: Convert this object's attributes into hash format. It will
35 | # automatically apply key ordering.
36 | #
37 | # Author:: alex.cavalli@offers.com
38 | #
39 | # === Parameters
40 | # * +keys_case+ - specifies the hash keys case, default 'underscore'
41 | # ==== keys_case
42 | # * :camelcase - CamelCase
43 | # * :underscore - underscore_case
44 | def to_hash(keys_case=:underscore)
45 | hash = super
46 | apply_order(hash)
47 | hash
48 | end
49 |
50 | private
51 |
52 |
53 | # Internal: Add an :order! key to the hash pointing to an array of all the
54 | # keys in the hash according to the order from the config file.
55 | #
56 | # Author: alex.cavalli@offers.com
57 | #
58 | # === Parameters
59 | # * +hash+ - the hash to apply an id-elevated order to
60 | def apply_order(hash)
61 | key_order = get_key_order
62 | return if key_order.empty?
63 | key_order = key_order.map { |key| key.to_s.camelcase }
64 | key_order = key_order & hash.keys # remove keys from order not present in hash
65 | hash[:order!] = key_order
66 | end
67 |
68 |
69 | # Internal: Retrieve the ordered array of keys corresponding to this data
70 | # object. Should be overriden by children.
71 | #
72 | # Author: alex.cavalli@offers.com
73 | def get_key_order
74 | []
75 | end
76 |
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/partial_errors.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines an array of error that occur when adding ads
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | class PartialErrors < BingAdsApi::DataObject
9 |
10 | attr_accessor :batch_errors
11 |
12 |
13 | # Public : Constructor
14 | #
15 | # Author:: jlopezn@neonline.cl
16 | #
17 | # attributes - Hash with the initial attributes
18 | # === Attributes
19 | # * tracking_id : the operation tracking id value
20 | # * batch_error : array of hashes with the batch errors
21 | def initialize(attributes={})
22 | super(attributes)
23 | if attributes
24 | initialize_batch_errors(attributes)
25 | end
26 | end
27 |
28 |
29 | # Public : Specified to string method
30 | #
31 | # Author:: jlopezn@neonline.cl
32 | def to_s
33 | str = " Batch Errors:\n"
34 | if batch_errors
35 | str += batch_errors.map{ |be| "\t" + be.to_s }.join("\n")
36 | end
37 |
38 | return str
39 | end
40 |
41 |
42 | private
43 |
44 | # Public : Helper method for the ApiFaultDetail constructor
45 | # to initialize the batch errors array
46 | #
47 | # Author:: jlopezn@neonline.cl
48 | #
49 | # batch_errors_hash - Hash with the :batch_errors key received from the SOAP request
50 | #
51 | # Returns:: none
52 | def initialize_batch_errors(batch_errors_hash)
53 | return if batch_errors_hash.nil?
54 |
55 | if batch_errors_hash[:batch_error].is_a?(Array)
56 | self.batch_errors = []
57 | batch_errors_hash[:batch_error].each do |be|
58 | self.batch_errors << init_batch_error(be)
59 | end
60 | elsif batch_errors_hash[:batch_error].is_a?(Hash)
61 | self.batch_errors = [ init_batch_error(batch_errors_hash[:batch_error]) ]
62 | end
63 | end
64 |
65 |
66 | def init_batch_error(batch_error_hash)
67 | if batch_error_hash.key?("@i:type".to_sym) && batch_error_hash["@i:type".to_sym] == "EditorialError"
68 | return BingAdsApi::EditorialError.new(batch_error_hash)
69 | else
70 | return BingAdsApi::BatchError.new(batch_error_hash)
71 | end
72 | end
73 | end
74 |
75 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api/config.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'singleton'
3 |
4 | module BingAdsApi
5 |
6 | # Public : Helper class for configuration issues like WSDL URLs and constants
7 | #
8 | # Author:: author@neonline.cl
9 | #
10 | # Examples
11 | # class_usage
12 | # # => class_usage_return
13 | class Config
14 | include Singleton
15 |
16 | # Array with Bing Ads API environments: +sandbox+ and +production+
17 | ENVIRONMENTS = ['sandbox', 'production']
18 |
19 | attr_accessor :config
20 | @config = YAML.load_file(File.join(File.dirname(__FILE__),"../bing-ads-api.yml"))
21 |
22 | # Public : Constructor
23 | #
24 | # Author:: jlopezn@neonline.cl
25 | def initialize
26 | @config = YAML.load_file(File.join(File.dirname(__FILE__),"../bing-ads-api.yml"))
27 | end
28 |
29 | # Public : Returns the config file as an Hash instance
30 | #
31 | # Author:: jlopezn@neonline.cl
32 | #
33 | # Returns:: Hash
34 | def self.hash_instance
35 | instance.config
36 | end
37 |
38 | ## Constants
39 | @config['constants'].each do |key, value|
40 |
41 | define_method("#{key.to_s}_constants") do |constant=nil|
42 | value[constant.to_s] if constant
43 | value
44 | end
45 |
46 | end
47 |
48 | ## Orders
49 | @config['orders'].each do |key, value|
50 |
51 | define_method("#{key.to_s}_orders") do |order=nil|
52 | value[order.to_s] if order
53 | value
54 | end
55 |
56 | end
57 |
58 | # Public : Returns a String with WSDL url for the service indicated
59 | #
60 | # Author:: jlopezn@neonline.cl
61 | #
62 | # === Parameters
63 | # environment - Bing Environment: 'sandbox' or 'production'
64 | # service - service name
65 | #
66 | # === Examples
67 | # config.service_wsdl(:sandbox, :campaign_management)
68 | # # => "https://api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/v9/CampaignManagementService.svc?singleWsdl"
69 | #
70 | # Returns:: returns
71 | # Raises:: exception
72 | def service_wsdl(environment, service)
73 | if (ENVIRONMENTS.include?(environment.to_s))
74 | if @config['wsdl'][environment.to_s].include?(service.to_s)
75 | return @config['wsdl'][environment.to_s][service.to_s]
76 | end
77 | raise "Unknown service '#{service.to_s}'. Available services: #{@config['wsdl'][environment.to_s].keys.join(", ")}"
78 | end
79 | raise "Invalid environment: #{environment}. Value should be 'sandbox' or 'production'"
80 | end
81 |
82 | end
83 |
84 | end
85 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/helpers/time_helper.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi::Helpers
4 |
5 | ##
6 | # Public : Utility module
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | module TimeHelper
11 |
12 | # Valid languages for reports
13 | TIME_PERIODS = BingAdsApi::Config.instance.
14 | reporting_constants['time_periods']
15 |
16 | # Public : Validates the time attribute present in some report request
17 | #
18 | # Author:: jlopezn@neonline.cl
19 | #
20 | # time - Hash with the time attribute for the report request
21 | #
22 | # Returns:: true if validation is ok, raises Exception otherwise
23 | # Raises:: Exception if custom date range is bad informed, or if time periods specified is unknown
24 | def valid_time(time)
25 | # Custom date range
26 | if time.is_a?(Hash)
27 | raise Exception.new("Invalid time: missing :custom_date_range_start key") if !time.key?(:custom_date_range_start)
28 | raise Exception.new("Invalid time: missing :custom_date_range_end key") if !time.key?(:custom_date_range_end)
29 | # Time periods
30 | else
31 | return TIME_PERIODS.key?(time.to_s)
32 | end
33 | return true
34 | end
35 |
36 |
37 | # Public : Return the time attribute of the ReportRequest as a valid Hash for SOAP requests
38 | #
39 | # Author:: jlopezn@neonline.cl
40 | #
41 | # keys_case - specifies the keys_case for the hash: :underscore or :camelcase
42 | #
43 | # Returns:: Hash
44 | def time_to_hash(keys_case)
45 | raise Exception.new("Invalid time value: nil") if self.time.nil?
46 |
47 | # Custom date range
48 | if time.is_a?(Hash)
49 | return {
50 | get_attribute_key("custom_date_range_end", keys_case) => {
51 | get_attribute_key("day", keys_case) => self.time[:custom_date_range_end][:day],
52 | get_attribute_key("month", keys_case) => self.time[:custom_date_range_end][:month],
53 | get_attribute_key("year", keys_case) => self.time[:custom_date_range_end][:year],
54 |
55 | },
56 | get_attribute_key("custom_date_range_start", keys_case) => {
57 | get_attribute_key("day", keys_case) => self.time[:custom_date_range_start][:day],
58 | get_attribute_key("month", keys_case) => self.time[:custom_date_range_start][:month],
59 | get_attribute_key("year", keys_case) => self.time[:custom_date_range_start][:year],
60 | }
61 | }
62 | # Time periods
63 | else
64 | return {"PredefinedTime" => TIME_PERIODS[time.to_s]}
65 | end
66 | end
67 |
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/spec/client_proxy_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 |
4 | # Author:: jlopezn@neonline.cl
5 | describe BingAdsApi::ClientProxy do
6 |
7 | let(:default_options) do
8 | {
9 | username: "ruby_bing_ads_sbx",
10 | password: "sandbox123",
11 | developer_token: "BBD37VB98",
12 | customer_id: "21025739",
13 | account_id: "8506945"
14 | }
15 | end
16 |
17 | it "should create client proxy" do
18 | options = {
19 | username: @username,
20 | password: @password,
21 | developer_token: @developer_token,
22 | customer_id: @customer_id,
23 | account_id: @account_id,
24 | wsdl_url: "https://campaign.api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/V10/CampaignManagementService.svc?singleWsdl"
25 | }
26 | client = BingAdsApi::ClientProxy.new(options)
27 |
28 | expect(client).not_to be_nil
29 | expect(client.service).not_to be_nil
30 | end
31 |
32 | it "should create client proxy with additional settings" do
33 | options = {
34 | username: @username,
35 | password: @password,
36 | developer_token: @developer_token,
37 | customer_id: @customer_id,
38 | account_id: @account_id,
39 | wsdl_url: "https://campaign.api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/V10/CampaignManagementService.svc?singleWsdl",
40 | proxy: {
41 | logger: Logger.new(STDOUT),
42 | encoding: "UTF-8"
43 | }
44 | }
45 | client = BingAdsApi::ClientProxy.new(options)
46 |
47 | expect(client).not_to be_nil
48 | expect(client.service).not_to be_nil
49 | end
50 |
51 | it "should call service" do
52 | options = default_options.merge(
53 | wsdl_url: "https://campaign.api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/V10/CampaignManagementService.svc?singleWsdl"
54 | )
55 |
56 | client = BingAdsApi::ClientProxy.new(options)
57 | expect(client).not_to be_nil
58 |
59 | response = client.service.call(:get_campaigns_by_account_id,
60 | message: { account_id: client.account_id})
61 | expect(response).not_to be_nil
62 | end
63 |
64 | it "should create and call from config" do
65 | config = BingAdsApi::Config.instance
66 | options = default_options.merge(
67 | wsdl_url: config.service_wsdl(:sandbox, :campaign_management)
68 | )
69 |
70 | client = BingAdsApi::ClientProxy.new(options)
71 | expect(client).not_to be_nil
72 |
73 | response = client.service.call(:get_campaigns_by_account_id,
74 | message: { account_id: client.account_id})
75 | expect(response).not_to be_nil
76 | end
77 |
78 | end
79 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/performance_report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base class for 'performance report requests'.
7 | # Do not instantiate this object. Instead, use BingAdsApi::AccountPerformanceReportRequest,
8 | # BingAdsApi::CampaignPerformanceReportRequest, BingAdsApi::AdGroupPerformanceReportRequest or
9 | # BingAdsApi::AdPerformanceReportRequest
10 | #
11 | # Reference: http://msdn.microsoft.com/en-us/library/bing-ads-reporting-bing-ads-reportrequest.aspx
12 | #
13 | # Author:: jlopezn@neonline.cl
14 | class PerformanceReportRequest < BingAdsApi::ReportRequest
15 |
16 | # Adds helper methods to time attribute
17 | include BingAdsApi::Helpers::TimeHelper
18 |
19 | # Adds helper methods to column attribute
20 | include BingAdsApi::Helpers::ColumnHelper
21 |
22 | # Adds helper methods to filter attribute
23 | include BingAdsApi::Helpers::FilterHelper
24 |
25 |
26 | # Valid aggregations for reports
27 | AGGREGATIONS = BingAdsApi::Config.instance.
28 | reporting_constants['aggregation']
29 |
30 |
31 | attr_accessor :aggregation, :columns, :filter, :scope, :time
32 |
33 |
34 | # Public : Constructor. Adds validations to aggregations and time
35 | #
36 | # Author:: jlopezn@neonline.cl
37 | #
38 | # === Parameters
39 | # attributes - Hash with Performance report request
40 | #
41 | def initialize(attributes={})
42 | raise Exception.new("Invalid aggregation '#{attributes[:aggregation]}'") if !valid_aggregation(attributes[:aggregation])
43 | raise Exception.new("Invalid time") if !valid_time(attributes[:time])
44 | super(attributes)
45 | end
46 |
47 |
48 | # Public:: Returns this object as a Hash for SOAP Requests
49 | #
50 | # Author:: jlopezn@neonline.cl
51 | #
52 | # === Parameters
53 | # * +keys_case+ - specifies the case for the hash keys
54 | # ==== keys_case
55 | # * :camelcase - CamelCase
56 | # * :underscore - underscore_case
57 | #
58 | # === Examples
59 | # performance_report_request.to_hash(:camelcase)
60 | # # => {"Format"=>"Xml", "Language"=>"English", "ReportName"=>"My Report", "Aggregation"=>"Hourly", "Time"=>"Today", "ReturnOnlyCompleteData"=>false}
61 | #
62 | # Returns:: Hash
63 | def to_hash(keys_case = :underscore)
64 | hash = super(keys_case)
65 | hash[get_attribute_key('aggregation', keys_case)] = AGGREGATIONS[self.aggregation.to_s]
66 | hash[get_attribute_key('time', keys_case)] = time_to_hash(keys_case)
67 | return hash
68 | end
69 |
70 | private
71 |
72 | def valid_aggregation(aggregation)
73 | return AGGREGATIONS.key?(aggregation.to_s)
74 | end
75 |
76 | end
77 |
78 | end
--------------------------------------------------------------------------------
/spec/helpers/bing_ads_factory.rb:
--------------------------------------------------------------------------------
1 | class BingAdsFactory
2 |
3 | # Helper method to create a campaign on the remote API. Returns the created
4 | # campaign id.
5 | def self.create_campaign
6 | name = "Test Campaign #{SecureRandom.uuid}"
7 | campaigns = [
8 | BingAdsApi::Campaign.new(
9 | budget_type: BingAdsApi::Campaign::DAILY_BUDGET_STANDARD,
10 | daily_budget: 2000,
11 | daylight_saving: "false",
12 | description: name + " description",
13 | name: name + " name",
14 | time_zone: BingAdsApi::Campaign::SANTIAGO
15 | )
16 | ]
17 | response = service.add_campaigns(account_id, campaigns)
18 | response[:campaign_ids][:long]
19 | end
20 |
21 | # Helper method to create an ad group on the remote API. Returns the created
22 | # ad group id.
23 | def self.create_ad_group(campaign_id)
24 | name = "Ad Group #{SecureRandom.uuid}"
25 | ad_groups = [
26 | BingAdsApi::AdGroup.new(
27 | ad_distribution: BingAdsApi::AdGroup::SEARCH,
28 | language: BingAdsApi::AdGroup::SPANISH,
29 | name: name + " name",
30 | pricing_model: BingAdsApi::AdGroup::CPC,
31 | bidding_model: BingAdsApi::AdGroup::KEYWORD
32 | )
33 | ]
34 | response = service.add_ad_groups(campaign_id, ad_groups)
35 | response[:ad_group_ids][:long]
36 | end
37 |
38 | # Helper method to create an ad on the remote API. Returns the created ad id.
39 | def self.create_text_ad(ad_group_id)
40 | text_ad = BingAdsApi::TextAd.new(
41 | status: BingAdsApi::Ad::ACTIVE,
42 | destination_url: "http://www.adxion.com",
43 | display_url: "AdXion.com",
44 | text: "Text Ad #{SecureRandom.uuid}",
45 | title: "Text Ad"
46 | )
47 | response = service.add_ads(ad_group_id, text_ad)
48 | response[:ad_ids][:long]
49 | end
50 |
51 | # Helper method to create a keyword on the remote API. Returns the created
52 | # keyword id.
53 | def self.create_keyword(ad_group_id)
54 | keyword = BingAdsApi::Keyword.new(
55 | bid: BingAdsApi::Bid.new(amount: 1.23),
56 | destination_url: "http://www.adxion.com",
57 | match_type: BingAdsApi::Keyword::EXACT,
58 | status: BingAdsApi::Keyword::ACTIVE,
59 | text: "Keyword #{SecureRandom.uuid}"
60 | )
61 | response = service.add_keywords(ad_group_id, keyword)
62 | response[:keyword_ids][:long]
63 | end
64 |
65 | def self.service
66 | @service ||= BingAdsApi::CampaignManagement.new(
67 | environment: :sandbox,
68 | username: "ruby_bing_ads_sbx",
69 | password: "sandbox123",
70 | developer_token: "BBD37VB98",
71 | customer_id: "21025739",
72 | account_id: account_id
73 | )
74 | end
75 |
76 | def self.account_id
77 | "8506945"
78 | end
79 |
80 | end
81 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object for all report requests.
7 | # Do not instantiate this object. Instead, you may instantiate one
8 | # of the following report request objects which derives from this object to request a report.
9 | #
10 | # Reference: http://msdn.microsoft.com/en-us/library/bing-ads-reporting-bing-ads-reportrequest.aspx
11 | #
12 | # Author:: jlopezn@neonline.cl
13 | class ReportRequest < BingAdsApi::DataObject
14 |
15 | # Valid Formats for reports
16 | FORMATS = BingAdsApi::Config.instance.
17 | reporting_constants['format']
18 |
19 | # Valid languages for reports
20 | LANGUAGES = BingAdsApi::Config.instance.
21 | reporting_constants['language']
22 |
23 | # Valid report request status for reports
24 | REQUEST_STATUS = BingAdsApi::Config.instance.
25 | reporting_constants['request_status_type']
26 |
27 | attr_accessor :format, :language, :report_name, :return_only_complete_data
28 |
29 | # Public : Constructor. Adds validations to format and language attributes
30 | #
31 | # Author:: jlopezn@neonline.cl
32 | #
33 | # === Parameters
34 | # attributes - Hash with Report request parameters
35 | def initialize(attributes={})
36 | raise Exception.new("Invalid format '#{attributes[:format]}'") if !valid_format(attributes[:format])
37 | raise Exception.new("Invalid language '#{attributes[:language]}'") if !valid_language(attributes[:language])
38 | super(attributes)
39 | self.return_only_complete_data = attributes[:return_only_complete_data] || false
40 | end
41 |
42 |
43 | # Public:: Returns this object as a Hash for SOAP Requests
44 | #
45 | # Author:: jlopezn@neonline.cl
46 | #
47 | # === Parameters
48 | # * +keys_case+ - specifies the case for the hash keys
49 | # ==== keys_case
50 | # * :camelcase - CamelCase
51 | # * :underscore - underscore_case
52 | #
53 | # === Examples
54 | # report_request.to_hash(:camelcase)
55 | # # => {"Format"=>"Xml", "Language"=>"English", "ReportName"=>"My Report", "ReturnOnlyCompleteData"=>true}
56 | #
57 | # Returns:: Hash
58 | def to_hash(keys_case = :underscore)
59 | hash = super(keys_case)
60 | hash[get_attribute_key('format', keys_case)] = FORMATS[self.format.to_s]
61 | hash[get_attribute_key('language', keys_case)] = LANGUAGES[self.language.to_s]
62 | return hash
63 | end
64 |
65 | private
66 |
67 | def valid_format(format)
68 | return FORMATS.key?(format.to_s)
69 | end
70 |
71 |
72 | def valid_language(language)
73 | return LANGUAGES.key?(language.to_s)
74 | end
75 |
76 | # Internal: Retrieve the ordered array of keys corresponding to this data
77 | # object.
78 | #
79 | # Author: alex.cavalli@offers.com
80 | def get_key_order
81 | super.concat(BingAdsApi::Config.instance.
82 | reporting_orders['report_request'])
83 | end
84 |
85 | end
86 |
87 | end
88 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/service/customer_management.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | module BingAdsApi
3 |
4 |
5 | # Public : This class represents the Customer Management Services
6 | # defined in the Bing Ads API, to manage customer accounts
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | # Examples
11 | # options = {
12 | # :environment => :sandbox,
13 | # :username => "username",
14 | # :password => "pass",
15 | # :developer_token => "SOME_TOKEN",
16 | # :customer_id => "1234567",
17 | # :account_id => "9876543" }
18 | # service = BingAdsApi::CustomerManagement.new(options)
19 | class CustomerManagement < BingAdsApi::Service
20 |
21 |
22 | # Public : Constructor
23 | #
24 | # Author:: jlopezn@neonline.cl
25 | #
26 | # options - Hash with the parameters for the client proxy and the environment
27 | #
28 | # Examples
29 | # options = {
30 | # :environment => :sandbox,
31 | # :username => "username",
32 | # :password => "password",
33 | # :developer_token => "DEV_TOKEN",
34 | # :customer_id => "123456",
35 | # :account_id => "654321"
36 | # }
37 | # service = BingAdsApi::CustomerManagement.new(options)
38 | def initialize(options={})
39 | super(options)
40 | end
41 |
42 |
43 | #########################
44 | ## Operations Wrappers ##
45 | #########################
46 |
47 | # Public : Gets a list of objects that contains account identification information,
48 | # for example the name and identifier of the account, for the specified customer.
49 | #
50 | # Author:: jlopezn@neonline.cl
51 | #
52 | # === Parameters
53 | # +customer_id+ - identifier for the customer who owns the accounts. If nil, then the authentication customer id is used
54 | # +only_parent_accounts+ - boolean to determine whether to return only the accounts that belong to the customer or to also
55 | # return the accounts that the customer manages for other customers. Default false
56 | #
57 | # === Examples
58 | # customer_management_service.get_accounts_info
59 | # # => Array[BingAdsApi::AccountsInfo]
60 | #
61 | # Returns:: Array of BingAdsApi::AccountsInfo
62 | #
63 | # Raises:: exception
64 | def get_accounts_info(customer_id=nil, only_parent_accounts=false)
65 | response = call(:get_accounts_info,
66 | {customer_id: customer_id || self.client_proxy.customer_id,
67 | only_parent_accounts: only_parent_accounts.to_s})
68 | response_hash = get_response_hash(response, __method__)
69 | accounts = response_hash[:accounts_info][:account_info]
70 | if accounts.is_a?(Array)
71 | accounts.map! do |account_hash|
72 | BingAdsApi::AccountInfo.new(account_hash)
73 | end
74 | else
75 | # when there is only one account api does not return an array but a single object
76 | accounts = [BingAdsApi::AccountInfo.new(accounts)]
77 | end
78 | return accounts
79 | end
80 |
81 |
82 | private
83 | def get_service_name
84 | "customer_management"
85 | end
86 |
87 | end
88 |
89 | end
90 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/fault/api_fault_detail.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines a fault object that operations return when web service-specific errors occur,
6 | # such as when the request message contains incomplete or invalid data.
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | class ApiFaultDetail < BingAdsApi::ApplicationFault
10 |
11 | attr_accessor :batch_errors, :operation_errors
12 |
13 |
14 | # Public : Constructor
15 | #
16 | # Author:: jlopezn@neonline.cl
17 | #
18 | # attributes - Hash with the initial attributes
19 | # === Attributes
20 | # * tracking_id : the operation tracking id value
21 | # * batch_errors : array of hashes with the batch errors
22 | # * operation_errors : array of hashes with the operation errors
23 | def initialize(attributes={})
24 | super(attributes)
25 | if attributes
26 | initialize_batch_errors(attributes[:batch_errors])
27 | initialize_operations_errors(attributes[:operation_errors])
28 | end
29 | end
30 |
31 |
32 | # Public : Specific to string
33 | #
34 | # Author:: jlopezn@neonline.cl
35 | #
36 | # Returns:: the object 'stringified'
37 | def to_s
38 | str = super.to_s + ":\n"
39 | if batch_errors
40 | str += "\tBatch Errors:\n" + batch_errors.map{ |be| "\t" + be.to_s }.join("\n")
41 | end
42 |
43 | if operation_errors
44 | str += "\tOperation Errors:\n" + operation_errors.map{ |oe| "\t" + oe.to_s }.join("\n")
45 | end
46 | return str
47 | end
48 |
49 |
50 | private
51 |
52 | # Public : Helper method for the ApiFaultDetail constructor
53 | # to initialize the batch errors array
54 | #
55 | # Author:: jlopezn@neonline.cl
56 | #
57 | # batch_errors_hash - Hash with the :batch_errors key received from the SOAP request
58 | #
59 | # Returns:: none
60 | def initialize_batch_errors(batch_errors_hash)
61 | return if batch_errors_hash.nil?
62 |
63 | if batch_errors_hash[:batch_error].is_a?(Array)
64 | self.batch_errors = []
65 | batch_errors_hash[:batch_error].each do |be|
66 | self.batch_errors << BingAdsApi::BatchError.new(be)
67 | end
68 | elsif batch_errors_hash[:batch_error].is_a?(Hash)
69 | self.batch_errors = [BingAdsApi::BatchError.new(batch_errors_hash[:batch_error])]
70 | end
71 | end
72 |
73 |
74 | # Public : Helper method for the ApiFaultDetail constructor
75 | # to initialize the operation errors array
76 | #
77 | # Author:: jlopezn@neonline.cl
78 | #
79 | # operation_errors_hash - Hash with the :operation_errors key received from the SOAP request
80 | #
81 | # Returns:: none
82 | def initialize_operations_errors(operation_errors_hash)
83 | return if operation_errors_hash.nil?
84 |
85 | if operation_errors_hash[:operations_error].is_a?(Array)
86 | self.operation_errors = []
87 | operation_errors_hash[:operation_error].each do |oe|
88 | self.operation_errors << BingAdsApi::OperationError.new(oe)
89 | end
90 | elsif operation_errors_hash[:operation_error].is_a?(Hash)
91 | self.operation_errors = [BingAdsApi::OperationError.new(operation_errors_hash[:operation_error])]
92 | end
93 |
94 | end
95 | end
96 |
97 | end
--------------------------------------------------------------------------------
/lib/bing-ads-api/service/customer_billing.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | module BingAdsApi
3 |
4 |
5 | # Public : This class represents the Campaign Management Services
6 | # defined in the Bing Ads API, to manage advertising campaigns
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | # Examples
11 | # options = {
12 | # :environment => :sandbox,
13 | # :username => "username",
14 | # :password => "pass",
15 | # :developer_token => "SOME_TOKEN",
16 | # :customer_id => "1234567",
17 | # :account_id => "9876543" }
18 | # service = BingAdsApi::CampaignManagement.new(options)
19 | class CustomerBilling < BingAdsApi::Service
20 |
21 |
22 | # Public : Constructor
23 | #
24 | # Author:: jlopezn@neonline.cl
25 | #
26 | # options - Hash with the parameters for the client proxy and the environment
27 | #
28 | # Examples
29 | # options = {
30 | # :environment => :sandbox,
31 | # :username => "username",
32 | # :password => "password",
33 | # :developer_token => "DEV_TOKEN",
34 | # :customer_id => "123456",
35 | # :account_id => "654321"
36 | # }
37 | # service = BingAdsApi::CampaignManagement.new(options)
38 | def initialize(options={})
39 | super(options)
40 | end
41 |
42 |
43 | #########################
44 | ## Operations Wrappers ##
45 | #########################
46 |
47 |
48 | # Public : Returns all the insertion orders found in the specified account
49 | #
50 | # Author:: dmitrii@webstreak.com
51 | #
52 | # === Parameters
53 | # account_id
54 | #
55 | # === Examples
56 | # customer_billing_service.get_insertion_orders_by_account_id(1)
57 | # # => Array[1,2,3]
58 | #
59 | # Returns:: Array of Insertion Orders
60 | #
61 | # Raises:: exception
62 | def get_insertion_orders_by_account_id(account_id)
63 | response = call(:get_insertion_orders_by_account,
64 | { account_id: account_id } )
65 | response_hash = get_response_hash(response, __method__)
66 | #campaign_ids = response_hash[:campaign_id_collection][:id_collection][:ids][:long]
67 | #return campaign_ids
68 | return response_hash
69 | end
70 |
71 | # Public : Returns insertion orders based on specific conditions
72 | #
73 | # Author:: dmitrii@webstreak.com
74 | #
75 | # === Parameters
76 | # ordering
77 | # page_info
78 | # predicates
79 | #
80 | # Returns:: Array of Insertion Orders
81 | #
82 | # Raises:: exception
83 | def search_insertion_orders(account_id)
84 | #predicate = BingAdsApi::Predicate.new(field: 'AccountId', operator: 'Equals', value: account_id)
85 | response = call(:search_insertion_orders,
86 | { predicates: {
87 | predicate: { field: 'AccountId', operator: 'Equals', value: account_id }
88 | }
89 | }
90 | )
91 | response_hash = get_response_hash(response, __method__)
92 | #campaign_ids = response_hash[:campaign_id_collection][:id_collection][:ids][:long]
93 | #return campaign_ids
94 | return response_hash
95 | end
96 |
97 |
98 | private
99 |
100 | def get_service_name
101 | "customer_billing"
102 | end
103 |
104 |
105 |
106 | end
107 |
108 | end
109 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/bulk/bulk_upload_status.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | require 'open-uri'
4 |
5 | module BingAdsApi
6 |
7 | ##
8 | # Public: Wrapper class for GetBulkUploadStatusResponse. Not an
9 | # actual data object in the Bing API.
10 | #
11 | # http://msdn.microsoft.com/en-US/library/dn600289.aspx
12 | #
13 | # Author:: alex.cavalli@offers.com
14 | class BulkUploadStatus < BingAdsApi::DataObject
15 |
16 | # Valid request status
17 | REQUEST_STATUS = BingAdsApi::Config.instance.
18 | bulk_constants['request_status_type']
19 |
20 | attr_accessor :errors, :forward_compatibility_map, :percent_complete,
21 | :request_id, :request_status, :result_file_url
22 |
23 | # Public:: Returns true if the status is completed
24 | #
25 | # Author:: alex.cavalli@offers.com
26 | #
27 | # Returns:: boolean
28 | def completed?
29 | return request_status == REQUEST_STATUS['completed']
30 | end
31 |
32 | # Public:: Returns true if the status is completed with errors
33 | #
34 | # Author:: alex.cavalli@offers.com
35 | #
36 | # Returns:: boolean
37 | def completed_with_errors?
38 | return request_status == REQUEST_STATUS['completed_with_errors']
39 | end
40 |
41 | # Public:: Returns true if the status is failed
42 | #
43 | # Author:: alex.cavalli@offers.com
44 | #
45 | # Returns:: boolean
46 | def failed?
47 | return request_status == REQUEST_STATUS['failed']
48 | end
49 |
50 | # Public:: Returns true if the status is file uploaded
51 | #
52 | # Author:: alex.cavalli@offers.com
53 | #
54 | # Returns:: boolean
55 | def file_uploaded?
56 | return request_status == REQUEST_STATUS['file_uploaded']
57 | end
58 |
59 | # Public:: Returns true if the request_status is in progress
60 | #
61 | # Author:: alex.cavalli@offers.com
62 | #
63 | # Returns:: boolean
64 | def in_progress?
65 | return request_status == REQUEST_STATUS['in_progress']
66 | end
67 |
68 | # Public:: Returns true if the status is pending file upload
69 | #
70 | # Author:: alex.cavalli@offers.com
71 | #
72 | # Returns:: boolean
73 | def pending_file_upload?
74 | return request_status == REQUEST_STATUS['pending_file_upload']
75 | end
76 |
77 | # Public:: Attempts to download the result file to the specified directory.
78 | # The file name will be the request id appended with ".zip".
79 | #
80 | # Author:: alex.cavalli@offers.com
81 | #
82 | # === Parameters
83 | # +target_directory+ - Directory or file in directory to download the upload
84 | # results file to.
85 | #
86 | # Returns:: The downloaded file path if success, or nil if failure.
87 | def download_result_file(target_directory)
88 | return nil unless completed? || completed_with_errors?
89 |
90 | dir = Dir.exists?(target_directory) ? target_directory : File.dirname(target_directory)
91 | raise ArgumentError, "Could not determine target directory from argument: #{target_directory}" unless Dir.exists?(dir)
92 |
93 | result_file_target = File.join(dir, "#{request_id}.zip")
94 |
95 | File.open(result_file_target, "wb") do |result_file|
96 | result_file << open(result_file_url).read
97 | end
98 | end
99 |
100 | end
101 |
102 | end
103 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/service/reporting.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | module BingAdsApi
3 |
4 | # Public : This class represents the Reporting Services
5 | # defined in the Bing Ads API, to request and download reports
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | #
9 | # Examples
10 | # options = {
11 | # :environment => :sandbox,
12 | # :username => "username",
13 | # :password => "pass",
14 | # :developer_token => "SOME_TOKEN",
15 | # :customer_id => "1234567",
16 | # :account_id => "9876543" }
17 | # service = BingAdsApi::Reporting.new(options)
18 | class Reporting < BingAdsApi::Service
19 |
20 |
21 | # Public : Get the status of a report request
22 | #
23 | # Author:: jlopezn@neonline.cl
24 | #
25 | # === Parameters
26 | # +report_request_id+ - Identifier of the report request
27 | #
28 | # === Examples
29 | # service.poll_generate_report("12345")
30 | # # => Hash
31 | #
32 | # Returns:: Hash with the PollGenerateReportResponse structure
33 | #
34 | # Raises:: exception
35 | def poll_generate_report(report_request_id)
36 | response = call(:poll_generate_report,
37 | {report_request_id: report_request_id} )
38 | response_hash = get_response_hash(response, __method__)
39 | report_request_status = BingAdsApi::ReportRequestStatus.new(
40 | response_hash[:report_request_status])
41 | return report_request_status
42 | end
43 |
44 | # Public : Submits a report request
45 | #
46 | # Author:: jlopezn@neonline.cl
47 | #
48 | # === Parameters
49 | # +report_request+ - a BingAdsApi::ReportRequest subclass instance
50 | #
51 | # === Examples
52 | # ==== CampaignPerformanceReportRequest
53 | # report_request = BingAdsApi::CampaignPerformanceReportRequest.new(:format => :xml,
54 | # :language => :english,
55 | # :report_name => "My Report",
56 | # :aggregation => :hourly,
57 | # :columns => [:account_name, :account_number, :time_period,
58 | # :campaign_name, :campaign_id, :status, :currency_code,
59 | # :impressions, :clicks, :ctr, :average_cpc, :spend,
60 | # :conversions, :conversion_rate, :cost_per_conversion],
61 | # :filter => {
62 | # # String as bing expected
63 | # :ad_distribution => "Search",
64 | # :device_os => "Windows",
65 | # # snake case symbol
66 | # :device_type => :computer,
67 | # # nil criteria
68 | # :status => nil
69 | # },
70 | # :scope => {:account_ids => 5978083,
71 | # :campaigns => [
72 | # {:account_id => 5978083, :campaign_id => 1951230156},
73 | # {:account_id => 5978083, :campaign_id => 1951245412},
74 | # {:account_id => 5978083, :campaign_id => 1951245474}]
75 | # },
76 | # :time => {
77 | # :custom_date_range_end => {:day => 31, :month => 12, :year => 2013},
78 | # :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
79 | # })
80 | # report_request_id = reporting_service.submit_generate_report(report_request)
81 | # # => "1234567890"
82 | #
83 | # Returns:: String with the requested report id
84 | #
85 | # Raises:: Exception if report_request is invalid or SOAP request failed
86 | def submit_generate_report(report_request)
87 | response = call(:submit_generate_report,
88 | {report_request: report_request.to_hash(:camelcase)})
89 | response_hash = get_response_hash(response, __method__)
90 | report_request_id = response_hash[:report_request_id]
91 | return report_request_id
92 | end
93 |
94 |
95 | private
96 | def get_service_name
97 | "reporting"
98 | end
99 |
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = BingAdsApi
2 |
3 | This project rocks and uses MIT-LICENSE.
4 |
5 | == Presentation
6 |
7 | This is a very simple, straight use gem for integrating any Rails app with the
8 | Bing Ads SOAP Webservices.
9 |
10 | == Installation
11 |
12 | Add to your Gemfile
13 | gem 'bing-ads-api'
14 |
15 | == Examples
16 | So far, I've just integrated some methods from +CampaignManagement+, +CustomerManagement+ and +Reporting+ services
17 |
18 |
19 | === Authentication
20 |
21 | In all cases, you must initialize some service object with the authentication options
22 | Here's an example with the CampaignMangement service object
23 |
24 | options = {
25 | :environment => :sandbox,
26 | :username => "desarrollo_neonline",
27 | :password => "neonline2013",
28 | :developer_token => "BBD37VB98",
29 | :customer_id => "21021746",
30 | :account_id => "5978083"
31 | }
32 | service = BingAdsApi::CampaignManagement.new(options)
33 |
34 | * +environment+ can be one of two values: +:sandbox+ or +:production+
35 |
36 |
37 | === Campaign Management
38 |
39 | Example of service object initialization:
40 | options = {
41 | :environment => :sandbox,
42 | :username => "desarrollo_neonline",
43 | :password => "neonline2013",
44 | :developer_token => "BBD37VB98",
45 | :customer_id => "21021746",
46 | :account_id => "5978083"
47 | }
48 | service = BingAdsApi::CampaignManagement.new(options)
49 |
50 |
51 | ==== Adding Campaigns
52 |
53 | account_id = 123456789
54 | campaigns = [
55 | BingAdsApi::Campaign.new(
56 | :budget_type => BingAdsApi::Campaign::DAILY_BUDGET_STANDARD,
57 | :conversion_tracking_enabled => "false",
58 | :daily_budget => 2000,
59 | :daylight_saving => "false",
60 | :description => name + " first description",
61 | :monthly_budget => 5400,
62 | :name => name + " first name",
63 | :status => BingAdsApi::Campaign::PAUSED,
64 | :time_zone => BingAdsApi::Campaign::SANTIAGO),
65 |
66 | BingAdsApi::Campaign.new(
67 | :budget_type => BingAdsApi::Campaign::DAILY_BUDGET_STANDARD,
68 | :conversion_tracking_enabled => "false",
69 | :daily_budget => 2500,
70 | :daylight_saving => "false",
71 | :description => name + " second description",
72 | :monthly_budget => 7800,
73 | :name => name + " second name",
74 | :status => BingAdsApi::Campaign::PAUSED,
75 | :time_zone => BingAdsApi::Campaign::SANTIAGO) ]
76 | response = service.add_campaigns(account_id, campaigns)
77 |
78 | In some cases the methods will return a Hash with the 'response' tag according
79 | to the method invoked. Like the example above.
80 |
81 |
82 | ==== Find Campaigns
83 |
84 | In other cases, like this next example, the method will return an object, like an array or
85 | a object representation of the hash
86 | campaigns = service.get_campaigns_by_account_id(123456789)
87 | # => campaigns is an array of BingAdsApi::Campaign
88 |
89 |
90 | === Customer Management
91 |
92 | Example of service object initialization:
93 | options = {
94 | :environment => :sandbox,
95 | :username => "desarrollo_neonline",
96 | :password => "neonline2013",
97 | :developer_token => "BBD37VB98",
98 | :customer_id => "21021746",
99 | :account_id => "5978083"
100 | }
101 | service = BingAdsApi::CustomerManagement.new(options)
102 |
103 | ==== Get accounts info
104 | accounts = service.get_accounts_info
105 | # => accounts is an array of BingAdsApi::AccountsInfo
106 |
107 | For this method you can also specify a diferent customer id
108 | accounts = service.get_accounts_info(12345)
109 | # => accounts is an array of BingAdsApi::AccountsInfo
110 | That would give you the customer's 12345 accounts
111 |
112 |
113 |
114 | === Reporting
115 |
116 | ==== Submit generate report
117 |
118 |
119 | ==== Poll generate report
120 |
121 |
122 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/constants.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | require 'active_support/core_ext/string/inflections'
4 |
5 | module BingAdsApi
6 |
7 | # Public : Utility class for TimeZones values
8 | #
9 | # Example
10 | # BingAdsApi::TimeZone.SANTIAGO
11 | # # => 'Santiago'
12 | #
13 | # Author:: jlopezn@neonline.cl
14 | module TimeZone
15 |
16 |
17 | BingAdsApi::Config.instance.common_constants['time_zones'].each do |key, value|
18 | TimeZone.const_set(key.upcase, value)
19 | end
20 |
21 | end
22 |
23 | # Public : Utility class for AdLanguages values
24 | #
25 | # Example
26 | # BingAdsApi::AdLanguages.SPANISH
27 | # # => 'Spanish'
28 | # BingAdsApi::AdLanguages.SPANISH_CODE
29 | # # => 'ES'
30 | #
31 | # Author:: jlopezn@neonline.cl
32 | module AdLanguage
33 |
34 | BingAdsApi::Config.instance.common_constants['ad_languages'].each do |key, value|
35 | if key == 'codes'
36 | value.each do |code_key, code_value|
37 | AdLanguage.const_set("#{code_key.upcase}_CODE", code_value)
38 | end
39 | else
40 | AdLanguage.const_set(key.upcase, value)
41 | end
42 | end
43 |
44 | end
45 |
46 |
47 | ## Dynamic classes for campaign management constants
48 | BingAdsApi::Config.instance.campaign_management_constants.each do |const_key, const_value|
49 |
50 | const_module = Module.new do
51 | # Dynamically create Constants classes for each value found
52 | const_value.each do |key, value|
53 | self.const_set(key.upcase, value)
54 | end
55 |
56 | end
57 | BingAdsApi.const_set(const_key.camelize, const_module)
58 |
59 | end
60 |
61 |
62 | ## Dynamic classes for customer management constants
63 | BingAdsApi::Config.instance.customer_management_constants.each do |const_key, const_value|
64 |
65 | const_module = Module.new do
66 | # Dynamically create Constants classes for each value found
67 | const_value.each do |key, value|
68 | self.const_set(key.upcase, value)
69 | end
70 |
71 | end
72 | BingAdsApi.const_set(const_key.camelize, const_module)
73 |
74 | end
75 |
76 |
77 |
78 | # Public : Module for Reporting formats
79 | #
80 | # Example
81 | # BingAdsApi::ReportFormat.CSV
82 | # # => 'Csv'
83 | #
84 | # Author:: jlopezn@neonline.cl
85 | module ReportFormat
86 | BingAdsApi::Config.instance.reporting_constants['format'].each do |key, value|
87 | ReportFormat.const_set(key.upcase, value)
88 | end
89 | end
90 |
91 |
92 | # Public : Module for Reporting languages
93 | #
94 | # Example
95 | # BingAdsApi::ReportLanguage.ENGLISH
96 | # # => 'English'
97 | #
98 | # Author:: jlopezn@neonline.cl
99 | module ReportLanguage
100 | BingAdsApi::Config.instance.reporting_constants['language'].each do |key, value|
101 | ReportLanguage.const_set(key.upcase, value)
102 | end
103 | end
104 |
105 |
106 | # Public : Module for Reporting languages
107 | #
108 | # Example
109 | # BingAdsApi::ReportAggregation.SUMMARY
110 | # # => 'Summary'
111 | # BingAdsApi::ReportAggregation.HOURLY
112 | # # => 'Hourly'
113 | #
114 | # Author:: jlopezn@neonline.cl
115 | module ReportAggregation
116 | BingAdsApi::Config.instance.reporting_constants['aggregation'].each do |key, value|
117 | ReportAggregation.const_set(key.upcase, value)
118 | end
119 | end
120 |
121 |
122 | # Public : Module for Reporting languages
123 | #
124 | # Example
125 | # BingAdsApi::ReportTimePeriods.TODAY
126 | # # => 'Today'
127 | # BingAdsApi::ReportTimePeriods.LAST_WEEK
128 | # # => 'LastWeek'
129 | #
130 | # Author:: jlopezn@neonline.cl
131 | module ReportTimePeriods
132 | BingAdsApi::Config.instance.reporting_constants['time_periods'].each do |key, value|
133 | ReportTimePeriods.const_set(key.upcase, value)
134 | end
135 | end
136 |
137 |
138 | ## Dynamic classes for reporting constants
139 | # BingAdsApi::Config.instance.reporting_constants.each do |const_key, const_value|
140 | #
141 | # const_module = Module.new do
142 | # # Dynamically create Constants classes for each value found
143 | # const_value.each do |key, value|
144 | # self.const_set(key.upcase, value)
145 | # end
146 | #
147 | # end
148 | # BingAdsApi.const_set(const_key.camelize, const_module)
149 | #
150 | # end
151 |
152 | end
153 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/ad_group.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Defines an ad group.
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | #
9 | # Examples
10 | # ad_group = BingAdsApi::AdGroup.new(
11 | # :ad_distribution => BingAdsApi::AdGroup::SEARCH,
12 | # :language => BingAdsApi::AdGroup::SPANISH,
13 | # :name => "Ad Group name",
14 | # :pricing_model => BingAdsApi::AdGroup::CPC,
15 | # :bidding_model => BingAdsApi::AdGroup::KEYWORD)
16 | # # =>
17 | class AdGroup < BingAdsApi::DataObject
18 | include BingAdsApi::AdDistribution
19 | include BingAdsApi::AdRotationType
20 | include BingAdsApi::PricingModel
21 | include BingAdsApi::BiddingModel
22 | include BingAdsApi::AdLanguage
23 | include BingAdsApi::AdGroupStatus
24 |
25 |
26 | attr_accessor :id,
27 | :ad_distribution,
28 | :ad_rotation,
29 |
30 | :broad_match_bid,
31 | :content_match_bid,
32 | :exact_match_bid,
33 | :phrase_match_bid,
34 | :search_bid,
35 |
36 | :bidding_model,
37 | :pricing_model,
38 |
39 | :language,
40 | :name,
41 | :status,
42 |
43 | :start_date,
44 | :end_date
45 |
46 |
47 |
48 | # Public : Constructor in a ActiveRecord style, with a hash of attributes as input
49 | #
50 | # Author:: jlopezn@neonline.cl
51 | #
52 | # attributes - Hash with the objects attributes
53 | def initialize(attributes={})
54 | super(attributes)
55 | set_custom_attributes(attributes)
56 | end
57 |
58 |
59 | # Public : Returns this object as a hash to SOAP requests
60 | # This methods is a specialization for the +DataObject#to_hash+ method
61 | # that ads specific hash keys for the AdGroup object
62 | #
63 | # Author:: jlopezn@neonline.cl
64 | #
65 | # keys - specifies the keys case: CamelCase or underscore_case
66 | #
67 | # Returns:: Hash
68 | def to_hash(keys = :underscore)
69 | hash = super(keys)
70 |
71 | amount_key = get_attribute_key("amount", keys)
72 | if self.content_match_bid
73 | #hash.delete(:content_match_bid)
74 | hash[get_attribute_key("content_match_bid", keys)] = {amount_key => self.content_match_bid}
75 | end
76 |
77 | if self.exact_match_bid
78 | hash[get_attribute_key("exact_match_bid", keys)] = {amount_key => self.exact_match_bid}
79 | end
80 |
81 | if self.phrase_match_bid
82 | hash[get_attribute_key("phrase_match_bid", keys)] = {amount_key => self.phrase_match_bid}
83 | end
84 |
85 | if self.broad_match_bid
86 | hash[get_attribute_key("broad_match_bid", keys)] = {amount_key => self.broad_match_bid}
87 | end
88 |
89 | if self.start_date
90 | hash[get_attribute_key("start_date", keys)] = date_to_hash(self.start_date, keys)
91 | end
92 |
93 | if self.end_date
94 | hash[get_attribute_key("end_date", keys)] = date_to_hash(self.end_date, keys)
95 | end
96 |
97 | return hash
98 | end
99 |
100 |
101 | private
102 | def set_custom_attributes(attributes)
103 | self.content_match_bid = attributes[:content_match_bid][:amount] if attributes.key?(:content_match_bid)
104 | self.exact_match_bid = attributes[:exact_match_bid][:amount] if attributes.key?(:exact_match_bid)
105 | self.phrase_match_bid = attributes[:phrase_match_bid][:amount] if attributes.key?(:phrase_match_bid)
106 | self.broad_match_bid = attributes[:broad_match_bid][:amount] if attributes.key?(:broad_match_bid)
107 |
108 | if attributes.key?(:start_date) && !attributes[:start_date].nil?
109 | self.start_date = DateTime.strptime(
110 | "#{attributes[:start_date][:year]}-#{attributes[:start_date][:month]}-#{attributes[:start_date][:day]}",
111 | "%Y-%m-%d")
112 | end
113 |
114 | if attributes.key?(:end_date) && !attributes[:end_date].nil?
115 | self.end_date = DateTime.strptime(
116 | "#{attributes[:end_date][:year]}-#{attributes[:end_date][:month]}-#{attributes[:end_date][:day]}",
117 | "%Y-%m-%d")
118 | end
119 |
120 | end
121 |
122 |
123 | # Internal: Retrieve the ordered array of keys corresponding to this data
124 | # object.
125 | #
126 | # Author: alex.cavalli@offers.com
127 | def get_key_order
128 | super.concat(BingAdsApi::Config.instance.
129 | campaign_management_orders['ad_group'])
130 | end
131 |
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/helpers/filter_helper.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi::Helpers
4 |
5 | ##
6 | # Public : Utility module for filter attribute in +ReportRequest+ derived classes
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | #
10 | module FilterHelper
11 |
12 | include BingAdsApi::SOAPHasheable
13 |
14 |
15 | # Valid filters values for each known criteria
16 | FILTERS_CRITERIA = BingAdsApi::Config.instance.
17 | reporting_constants['filters']
18 |
19 |
20 | # Internal : Validates the filter attribute at the ReporRequest initialization
21 | # At the first invalid filter criteria or value detected this method raises Exception.
22 | # If all filter criteria and values are ok, this method returns true
23 | # Valid filter criteria are validated against FILTERS_CRITERIA constant
24 | # Valid filter values are validated against FILTERS constant
25 | #
26 | # Author:: jlopezn@neonline.cl
27 | #
28 | # === Parameters
29 | # valid_filters - Hash with the set of valid filter values
30 | # filter - Hash with the filter criteria and values
31 | #
32 | # Returns:: true if filter is valid
33 | #
34 | # Raises:: Exception at the first invalid filter criteria o value
35 | def valid_filter(valid_filters, filter)
36 | if filter && filter.is_a?(Hash)
37 | filter.keys.each do |filter_key|
38 | # validates if filter criteria is recognized
39 | raise Exception.new("Invalid filter criteria '#{filter_key.to_s}'") if !valid_filters.key?(filter_key.to_s)
40 | # validates the filter criteria value
41 | valid_filter_value(filter_key, filter[filter_key])
42 | end
43 | end
44 | return true
45 | end
46 |
47 |
48 | # Internal : Validates a specific filter criteria and his value
49 | #
50 | # Author:: jlopezn@neonline.cl
51 | #
52 | # key - filter criteria to evaluate
53 | # value - filter criteria to be evaluate
54 | #
55 | # Returns:: true if validation runs ok. Raise exception otherwise
56 | #
57 | # Raises:: Exception if filter value provided is not valid
58 | def valid_filter_value(key, value)
59 | return true if value.nil?
60 | return true if solve_filter_value(key, value)
61 | end
62 |
63 |
64 | # Public : Returns the filter attribute as a Hash to SOAP requests
65 | #
66 | # Author:: jlopezn@neonline.cl
67 | #
68 | # filter - Hash with the filter values
69 | #
70 | # Returns:: Hash
71 | def filter_to_hash(valid_filters, keys_case=:undescore)
72 | hashed_filter = {}
73 | filter.each do |key, value|
74 | hashed_filter[get_attribute_key(key, keys_case)] = solve_filter_value(key, value)
75 | end
76 | return hashed_filter
77 | end
78 |
79 |
80 | # Internal:: Solves the Bing value for the given filter attribute
81 | #
82 | # Author:: jlopezn@neonline.cl
83 | #
84 | # === Parameters
85 | # * +filter_criteria+ - String or symbol with the filter attribute to be solved
86 | #
87 | # === Examples
88 | # solve_filter_value(:ad_distribution, :search)
89 | # # => "Search"
90 | #
91 | # solve_filter_value(:ad_distribution, :other)
92 | # # => Exception "Invalid filter name 'other' for 'ad_distribution' criteria"
93 | #
94 | # solve_filter_value(:ad_distribution, "Search")
95 | # # => "Search"
96 | #
97 | # solve_filter_value(:ad_distribution, "Other")
98 | # # => Exception "Invalid filter value 'Other' for 'ad_distribution' criteria"
99 | #
100 | # Returns:: String with the Bing value for the filter criteria.
101 | #
102 | # Raises:: Exception if the filter's criteria or value are unknown
103 | def solve_filter_value(filter_criteria, filter_value)
104 | filter_criteria_values = FILTERS_CRITERIA[filter_criteria.to_s]
105 | if filter_value.is_a?(String)
106 | if filter_criteria_values.value?(filter_value)
107 | return filter_value
108 | else
109 | raise Exception.new("Invalid filter value '#{filter_value}' for '#{filter_criteria}' criteria")
110 | end
111 | elsif filter_value.is_a?(Symbol)
112 | if filter_criteria_values.key?(filter_value.to_s)
113 | return filter_criteria_values[filter_value.to_s]
114 | else
115 | raise Exception.new("Invalid filter name '#{filter_value}' for '#{filter_criteria}' criteria")
116 | end
117 | elsif filter_value.is_a?(Array)
118 | if filter_value.first.is_a?(Numeric)
119 | filter_value = filter_value.map { |f| {"ins0:int" => f } }
120 | elsif filter_value.first.is_a?(String)
121 | filter_value = filter_value.map { |f| {"ins0:string" => f } }
122 | end
123 | return filter_value
124 | end
125 | return nil
126 | end
127 |
128 |
129 | end
130 | end
131 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/ad.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object of an ad.
7 | # Do not instantiate this object. Instead you can instantiate the
8 | # BingAdsApi::TextAd, BingAdsApi::MobileAd, or BingAdsApi::ProductAd
9 | # object that derives from this object.
10 | #
11 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-campaign-management-ad.aspx
12 | #
13 | # Author:: jlopezn@neonline.cl
14 | #
15 | class Ad < BingAdsApi::DataObject
16 | include BingAdsApi::AdEditorialStatus
17 | include BingAdsApi::AdStatus
18 | include BingAdsApi::AdType
19 |
20 |
21 | attr_accessor :id,
22 | :device_preference,
23 | :editorial_status,
24 | :status,
25 | :type,
26 | :final_urls,
27 | :text,
28 | :title_part_1,
29 | :title_part_2
30 |
31 | private
32 |
33 | # Internal: Retrieve the ordered array of keys corresponding to this data
34 | # object.
35 | #
36 | # Author: alex.cavalli@offers.com
37 | def get_key_order
38 | super.concat(BingAdsApi::Config.instance.
39 | campaign_management_orders['ad'])
40 | end
41 |
42 | end
43 |
44 | ##
45 | # Public : Defines a text ad.
46 | #
47 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-campaign-management-textad.aspx
48 | #
49 | # Author:: jlopezn@neonline.cl
50 | #
51 | class TextAd < BingAdsApi::Ad
52 |
53 | attr_accessor :destination_url,
54 | :display_url,
55 | :text,
56 | :title
57 |
58 | # Public : Specification of DataObject#to_hash method that ads the type attribute based on this specific class
59 | #
60 | # Author:: jlopezn@neonline.cl
61 | #
62 | # keys - specifies the keys case
63 | #
64 | # Returns:: Hash
65 | def to_hash(keys = :underscore)
66 | hash = super(keys)
67 | hash[:'@xsi:type'] = "#{ClientProxy::NAMESPACE}:TextAd"
68 | return hash
69 | end
70 |
71 | private
72 |
73 | # Internal: Retrieve the ordered array of keys corresponding to this data
74 | # object.
75 | #
76 | # Author: alex.cavalli@offers.com
77 | def get_key_order
78 | super.concat(BingAdsApi::Config.instance.
79 | campaign_management_orders['text_ad'])
80 | end
81 |
82 | end
83 |
84 | ##
85 | # Public : Defines expanded text ad.
86 | #
87 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-campaign-management-textad.aspx
88 | #
89 | # Author:: dmitrii@webstreak.com
90 | #
91 | class ExpandedTextAd < BingAdsApi::Ad
92 |
93 | attr_accessor :final_urls,
94 | :text,
95 | :title_part_1,
96 | :title_part_2,
97 | :path_1,
98 | :path_2
99 |
100 | # Public : Specification of DataObject#to_hash method that ads the type attribute based on this specific class
101 | #
102 | # Author:: jlopezn@neonline.cl
103 | #
104 | # keys - specifies the keys case
105 | #
106 | # Returns:: Hash
107 | def to_hash(keys = :underscore)
108 | hash = super(keys)
109 | hash[:'@xsi:type'] = "#{ClientProxy::NAMESPACE}:ExpandedTextAd"
110 | new_hash = {}
111 | hash['FinalUrls'].each_pair do |k,v|
112 | new_hash.merge!({k.downcase => v})
113 | end
114 | hash['FinalUrls'] = new_hash
115 | return hash
116 | end
117 |
118 | private
119 |
120 | # Internal: Retrieve the ordered array of keys corresponding to this data
121 | # object.
122 | #
123 | # Author: alex.cavalli@offers.com
124 | def get_key_order
125 | super.concat(BingAdsApi::Config.instance.
126 | campaign_management_orders['expanded_text_ad'])
127 | end
128 |
129 | end
130 |
131 |
132 |
133 |
134 | ##
135 | # Public : Defines a product ad.
136 | #
137 | # Reference: http://msdn.microsoft.com/en-US/library/bing-ads-productad-campaign-management.aspx
138 | #
139 | # Author:: jlopezn@neonline.cl
140 | #
141 | class ProductAd < BingAdsApi::Ad
142 |
143 | attr_accessor :promotional_text
144 |
145 | # Public : Specification of DataObject#to_hash method that ads the type attribute based on this specific class
146 | #
147 | # Author:: jlopezn@neonline.cl
148 | #
149 | # keys - specifies the keys case
150 | #
151 | # Returns:: Hash
152 | def to_hash(keys = :underscore)
153 | hash = super(keys)
154 | hash[:'@xsi:type'] = "#{ClientProxy::NAMESPACE}:ProductAd"
155 | return hash
156 | end
157 |
158 | private
159 |
160 | # Internal: Retrieve the ordered array of keys corresponding to this data
161 | # object.
162 | #
163 | # Author: alex.cavalli@offers.com
164 | def get_key_order
165 | super.concat(BingAdsApi::Config.instance.
166 | campaign_management_orders['product_ad'])
167 | end
168 |
169 | end
170 |
171 | end
172 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/share_of_voice_report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 | ##
5 | # Public: Reporting service request object for a share of voice report.
6 | #
7 | # Author:: colin.knox@offers.com
8 | #
9 | class ShareOfVoiceReportRequest < BingAdsApi::PerformanceReportRequest
10 | # Valid Columns for this report request
11 | COLUMNS = BingAdsApi::Config.instance.
12 | reporting_constants['share_of_voice_report']['columns']
13 |
14 | # Valid Filters for this report request
15 | FILTERS = BingAdsApi::Config.instance.
16 | reporting_constants['share_of_voice_report']['filter']
17 |
18 | attr_accessor :max_rows, :sort
19 |
20 | # Public : Constructor. Adds a validations for the columns and filter
21 | # attributes
22 | #
23 | # Author:: colin.knox@offers.com
24 | #
25 | # === Parameters
26 | # * +attributes+ - Hash with the report request attributes
27 | #
28 | # === Example
29 | #
30 | # request = BingAdsApi::ShareOfVoiceReportRequest.new(
31 | # :format => :xml,
32 | # :language => :english,
33 | # :report_name => "Me report",
34 | # :aggregation => :hourly,
35 | # :columns => [:account_name, :account_number, :time_period],
36 | # # The filter is specified as a hash
37 | # :filter => {
38 | # # specifies the Bing expected String value
39 | # :ad_distribution => "Search",
40 | # :ad_type => "Text",
41 | # :bid_match_type => "Exact",
42 | # :delivered_match_type => "Exact",
43 | # # specifies criteria as a snake case symbol
44 | # :device_type => :tablet,
45 | # :keyword_relevance => [3],
46 | # :keywords => ["bing ads"],
47 | # :language_code => ["EN"]},
48 | # :max_rows => 10,
49 | # :scope => {
50 | # :account_ids => [123456, 234567],
51 | # :campaigns => [],
52 | # :ad_groups => [] },
53 | # :sort => []
54 | # # predefined date
55 | # :time => :this_week)
56 | def initialize(attributes={})
57 | raise Exception.new("Invalid columns") if !valid_columns(COLUMNS, attributes[:columns])
58 | raise Exception.new("Invalid filters") if !valid_filter(FILTERS, attributes[:filter])
59 | super(attributes)
60 | end
61 |
62 |
63 | # Public:: Returns the object as a Hash valid for SOAP requests
64 | #
65 | # Author:: jlopezn@neonline.cl
66 | #
67 | # === Parameters
68 | # * +keys_case+ - case for the hashes keys: underscore or camelcase
69 | #
70 | # Returns:: Hash
71 | def to_hash(keys = :underscore)
72 | hash = super(keys)
73 | hash[get_attribute_key('columns', keys)] =
74 | columns_to_hash(COLUMNS, columns, keys)
75 | hash[get_attribute_key('filter', keys)] =
76 | filter_to_hash(FILTERS, keys)
77 | hash[get_attribute_key('scope', keys)] = scope_to_hash(keys)
78 | hash[get_attribute_key('max_rows', keys)] = max_rows if max_rows
79 | hash[get_attribute_key('sort', keys)] = sort_to_hash(keys) if sort
80 | hash["@xsi:type"] = type_attribute_for_soap
81 | return hash
82 | end
83 |
84 |
85 | private
86 |
87 | # Internal:: Returns the scope attribute as a hash for the SOAP request
88 | #
89 | # Author:: alex.cavalli@offers.com
90 | #
91 | # === Parameters
92 | # * +keys_case+ - case for the hash: underscore or camelcase
93 | #
94 | # Returns:: Hash
95 | def scope_to_hash(keys_case=:underscore)
96 | return {
97 | get_attribute_key('account_ids', keys_case) =>
98 | {"ins0:long" => object_to_hash(scope[:account_ids], keys_case) },
99 | get_attribute_key('ad_group', keys_case) =>
100 | { "AdGroupReportScope" => object_to_hash(scope[:ad_groups], keys_case) },
101 | get_attribute_key('campaigns', keys_case) =>
102 | { "CampaignReportScope" => object_to_hash(scope[:campaigns], keys_case) }
103 | }
104 | end
105 |
106 |
107 | # Internal:: Returns the sort attribute as an array for the SOAP request
108 | #
109 | # Author:: alex.cavalli@offers.com
110 | #
111 | # === Parameters
112 | # * +keys_case+ - case for the hash: underscore or camelcase
113 | #
114 | # Returns:: Array
115 | def sort_to_hash(keys_case=:underscore)
116 | sort.map { |sort_object| object_to_hash(sort_object, keys_case) }
117 | end
118 |
119 |
120 | # Internal:: Returns a string with type attribute for the ReportRequest SOAP tag
121 | #
122 | # Author:: jlopezn@neonline.cl
123 | #
124 | # Returns:: "v9:ShareOfVoiceReportRequest"
125 | def type_attribute_for_soap
126 | return BingAdsApi::ClientProxy::NAMESPACE.to_s + ":" +
127 | BingAdsApi::Config.instance.
128 | reporting_constants['share_of_voice_report']['type']
129 | end
130 |
131 | end
132 |
133 | end
134 |
--------------------------------------------------------------------------------
/spec/bulk_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 | require 'tempfile'
4 |
5 | # Author:: alex.cavalli@offers.com
6 | describe BingAdsApi::Bulk do
7 |
8 | def generate_bulk_upload_file
9 | raise ArgumentError, "Block is required" unless block_given?
10 | Tempfile.open(["bulk_upload", ".csv"]) do |tempfile|
11 | tempfile.binmode
12 | tempfile << "Type,Status,Id,Parent Id,Campaign,Sync Time,Time Zone,Budget,Budget Type,Name,Campaign Type\n"
13 | tempfile << "Format Version,,,,,,,,,4.0,\n"
14 | tempfile << "Account,,#{default_options[:account_id]},21025739,,,,,,,\n"
15 | tempfile << "Campaign,Active,,#{default_options[:account_id]},Test Campaign #{SecureRandom.uuid},,Santiago,2000,DailyBudgetStandard,,SearchAndContent\n"
16 | tempfile << "Campaign,Active,,#{default_options[:account_id]},Test Campaign #{SecureRandom.uuid},,Santiago,2000,DailyBudgetStandard,,SearchAndContent\n"
17 | tempfile.rewind
18 | yield tempfile.path
19 | end
20 | end
21 |
22 | let(:default_options) do
23 | {
24 | environment: :sandbox,
25 | username: "ruby_bing_ads_sbx",
26 | password: "sandbox123",
27 | developer_token: "BBD37VB98",
28 | customer_id: "21025739",
29 | account_id: "8506945"
30 | }
31 | end
32 | let(:service) { BingAdsApi::Bulk.new(default_options) }
33 |
34 | it "should initialize with options" do
35 | new_service = BingAdsApi::Bulk.new(default_options)
36 | expect(new_service).not_to be_nil
37 | end
38 |
39 | it "should download campaigns by account id" do
40 | campaign_id = BingAdsFactory.create_campaign
41 | account_ids = [default_options[:account_id]]
42 | entities = [:campaigns, :ad_groups, :keywords, :ads]
43 | options = {
44 | data_scope: :entity_performance_data,
45 | download_file_type: :csv,
46 | format_version: 4.0,
47 | last_sync_time_in_utc: "2001-10-26T21:32:52",
48 | location_target_version: "Latest",
49 | performance_stats_date_range: {
50 | custom_date_range_end: {day: 31, month: 12, year: 2013},
51 | custom_date_range_start: {day: 1, month: 12, year: 2013}
52 | }
53 | }
54 |
55 | download_request_id = nil
56 | expect{
57 | download_request_id = service.download_campaigns_by_account_ids(
58 | account_ids,
59 | entities,
60 | options
61 | )
62 | }.not_to raise_error
63 |
64 | expect(download_request_id).not_to be_nil
65 | end
66 |
67 | context "when a bulk request has been requested" do
68 |
69 | before :each do
70 | BingAdsFactory.create_campaign
71 | @download_request_id = service.download_campaigns_by_account_ids(
72 | [default_options[:account_id]],
73 | [:campaigns, :ad_groups, :keywords, :ads]
74 | )
75 | end
76 |
77 | it "should successfully get detailed response status" do
78 | bulk_download_status = nil
79 | expect{
80 | bulk_download_status = service.get_bulk_download_status(@download_request_id)
81 | }.not_to raise_error
82 |
83 | expect(bulk_download_status).not_to be_nil
84 |
85 | expect(bulk_download_status.failed?).to eq(false)
86 | end
87 |
88 | end
89 |
90 | it "should retrieve a bulk upload URL and request ID" do
91 | account_id = default_options[:account_id]
92 | options = {response_mode: :errors_and_results}
93 |
94 | upload_request = nil
95 | expect{
96 | upload_request = service.get_bulk_upload_url(
97 | account_id,
98 | options
99 | )
100 | }.not_to raise_error
101 |
102 | expect(upload_request[:request_id]).not_to be_nil
103 | expect(upload_request[:upload_url]).not_to be_nil
104 | end
105 |
106 | it "should submit a bulk upload file and return the request ID" do
107 | account_id = default_options[:account_id]
108 | options = {response_mode: :errors_and_results}
109 |
110 | upload_request_id = nil
111 | generate_bulk_upload_file do |upload_file|
112 | expect{
113 | upload_request_id = service.submit_bulk_upload_file(
114 | upload_file,
115 | account_id,
116 | options
117 | )
118 | }.not_to raise_error
119 | end
120 |
121 | expect(upload_request_id).not_to be_nil
122 | end
123 |
124 | context "when a bulk request has been requested" do
125 | it "should download result file" do
126 | account_id = default_options[:account_id]
127 | options = {response_mode: :errors_and_results}
128 |
129 | upload_request_id = nil
130 | generate_bulk_upload_file do |upload_file|
131 | upload_request_id = service.submit_bulk_upload_file(
132 | upload_file,
133 | account_id,
134 | options
135 | )
136 | end
137 |
138 | tries = 0
139 | loop do
140 | upload_status = service.get_bulk_upload_status(upload_request_id)
141 | if upload_status.failed? || upload_status.pending_file_upload? || tries == 5
142 | raise "Upload failed with status: #{upload_status.request_status}. Tried #{tries} times."
143 | end
144 |
145 | downloaded = upload_status.download_result_file(__FILE__)
146 | if downloaded
147 | # clean up downloaded file
148 | FileUtils.rm(downloaded)
149 | break # success
150 | end
151 | tries += 1
152 | sleep(1)
153 | end
154 | end
155 | end
156 |
157 | end
158 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/client_proxy.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : ClientProxy es un objeto para encapsular la conexión y request
6 | # de servicios a la API de Bing. En su inicialización requiere los datos
7 | # de autenticación y el WSDL al servicio que se requiere.
8 | #
9 | # Author:: jlopezn@neonline.cl
10 | #
11 | # Examples
12 | # # => hash con datos autenticación y WSDL
13 | # options = {
14 | # :username => "username",
15 | # :password => "password",
16 | # :developer_token => "THE_TOKEN",
17 | # :customer_id => "123456",
18 | # :account_id => "123456",
19 | # :wsdl_url => "https://api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/v9/CampaignManagementService.svc?singleWsdl"
20 | # }
21 | # # => Instancia de ClientProxy
22 | # client = BingAdsApi::ClientProxy.new(options)
23 | # # => Llamada a servicio 'GetCampaignsByAccountId'
24 | # response = client.service.call(:get_campaigns_by_account_id,
25 | # message: { Account_id: client.account_id})
26 | class ClientProxy
27 |
28 | # Public : Namespace para atributos bing. Hace referencia a la versión de API usada
29 | NAMESPACE = :v11
30 |
31 | # Public : Case empleado los nombres de atributos en los XML
32 | KEYS_CASE = :camelcase
33 |
34 |
35 | # Atributos del client proxy
36 | attr_accessor :username, :password, :developer_token, :authentication_token, :wsdl_url, :account_id, :customer_id, :service, :namespace
37 |
38 | # Public : Constructor
39 | #
40 | # Author:: jlopezn@neonline.cl
41 | #
42 | # === Parameters
43 | # options - Hash con valores de autenticación y WSDL
44 | #
45 | #
46 | # === Options
47 | # * username - Bing Ads username
48 | # * passwrod - Bing Ads user's sign-in password
49 | # * developer_token - client application's developer access token
50 | # * customer_id - identifier for the customer that owns the account
51 | # * account_id - identifier of the account that own the entities in the request
52 | # * wsdl_url - URL for the WSDL to be called
53 | # * proxy - Hash with any Savon Client additional options (such as header, logger or enconding)
54 | #
55 | # === Examples
56 | # options = {
57 | # :username => "username",
58 | # :password => "password",
59 | # :developer_token => "THE_TOKEN",
60 | # :customer_id => "123456",
61 | # :account_id => "123456",
62 | # :wsdl_url => "https://api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/v9/CampaignManagementService.svc?singleWsdl"
63 | # }
64 | # # => Instancia de ClientProxy
65 | # client = BingAdsApi::ClientProxy.new(options)
66 | #
67 | # Returns:: ClientProxy instance
68 | def initialize(options=nil)
69 | if options
70 | if options[:authentication_token]
71 | @authentication_token ||= options[:authentication_token]
72 | else
73 | @username ||= options[:username]
74 | @password ||= options[:password]
75 | end
76 | @developer_token ||= options[:developer_token]
77 | @wsdl_url ||= options[:wsdl_url]
78 | @account_id ||= options[:account_id]
79 | @customer_id ||= options[:customer_id]
80 | @namespace ||= options[:namespace]
81 | end
82 | self.service = get_proxy(options[:proxy])
83 | end
84 |
85 |
86 | # Public : Delegate for Savon::Client.call method
87 | #
88 | # Author:: jlopezn@neonline.cl
89 | #
90 | # === Parameters
91 | # service_name - Service to be called
92 | # message - Message for the service
93 | # options - Additional options for the service
94 | #
95 | # === Examples
96 | # client.call_service(:some_service_name, {key: value})
97 | # # =>
98 | #
99 | # Returns:: Response from the Savon::Client
100 | # Raises:: Savon::SOAPFault Savon::HTTPError Savon::InvalidResponseError
101 | def call(service_name, message, options={})
102 | self.service.call(service_name, message)
103 | end
104 |
105 |
106 | private
107 | # Internal : Wrapper for Savon client instances
108 | #
109 | # Author:: jlopezn@neonline.cl
110 | #
111 | # Examples
112 | # get_proxy
113 | # # =>
114 | #
115 | # Returns:: Savon client instance
116 | def get_proxy(client_settings)
117 | settings = {
118 | convert_request_keys_to: KEYS_CASE,
119 | wsdl: self.wsdl_url,
120 | namespace_identifier: NAMESPACE,
121 | soap_header: build_headers
122 | }
123 | settings.merge!(client_settings) if client_settings
124 |
125 | return Savon.client(settings)
126 | end
127 |
128 |
129 | def build_headers
130 | headers = {
131 | "#{NAMESPACE.to_s}:CustomerAccountId" => self.account_id,
132 | "#{NAMESPACE.to_s}:CustomerId" => self.customer_id,
133 | "#{NAMESPACE.to_s}:DeveloperToken" => self.developer_token,
134 | }
135 | if self.authentication_token
136 | headers["#{NAMESPACE.to_s}:AuthenticationToken"] = self.authentication_token
137 | else
138 | headers["#{NAMESPACE.to_s}:UserName"] = self.username
139 | headers["#{NAMESPACE.to_s}:Password"] = self.password
140 | end
141 | return headers
142 | end
143 | end
144 |
145 | end
146 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/soap_hasheable.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public:: Module to define an object as Hasheable for the SOAP requests
7 | #
8 | # Author:: jlopezn@neonline.cl
9 | module SOAPHasheable
10 |
11 |
12 | # Internal: Metodo custom para transformar a hash un objeto.
13 | # Se puede indicar si se desean las keys en formato CamelCase o underscore_case
14 | #
15 | # Author:: asaavedrab@neonline.cl
16 | #
17 | # === Parameters
18 | # * keys_case - indica si las keys del hash deben estar en formato
19 | # ** :camelcase - CamelCase
20 | # ** :underscore - underscore_case
21 | #
22 | # === Example
23 | # a=BusinessPartner.new
24 | # a.to_hash
25 | # # => {id => 1, name => "emol"}
26 | #
27 | # Returns:: Hash
28 | def to_hash(keys_case=:underscore)
29 | object_to_hash(self, keys_case)
30 | end
31 |
32 |
33 | # Internal: Internal method to return an object as a SOAP Request alike hash
34 | # If the object is an array, this methods executes object_to_hash for each item.
35 | # If the object is a Hash, this methods normalize the keys according to +keys_case+ and executes object_to_hash for each value
36 | # If the object is a String or Numeric, returns the object
37 | #
38 | # Author:: asaavedrab@neonline.cl, jlopezn@neonline.cl
39 | #
40 | # === Parameters
41 | # * +object+ - object instance to be hashed
42 | # * +keys_case+ - specifies the hash keys case, default 'underscore'
43 | # ==== keys_case
44 | # * :camelcase - CamelCase
45 | # * :underscore - underscore_case
46 | #
47 | # === Example:
48 | # a=Person.new
49 | # a.to_hash(:underscore)
50 | # # => {'id' => 1, 'full_name' => "John Doe"}
51 | #
52 | # a=Person.new
53 | # a.to_hash(:camelcase)
54 | # # => {'Id' => 1, 'FullName' => "John Doe"}
55 | #
56 | # a=[, , ]
57 | # a.to_hash(:underscore)
58 | # # => [{'id' => 1, 'full_name' => "John Doe"}, {'id' => 2, 'full_name' => "Ms Mary"}, {'id' => 3, 'name' => "Mr Whiskers", 'color' => "Gray"}]
59 | #
60 | # Returns:: Hash
61 | def object_to_hash(object, keys_case=:underscore)
62 |
63 | # Nil safe
64 | return nil if object.nil?
65 |
66 | # In case of hash, we only normalize the keys values
67 | return normalize_hash_keys(object, keys_case) if object.is_a?(Hash)
68 | # In case of array, we make hasheable each element
69 | return object.collect{ |item| object_to_hash(item, keys_case) } if object.is_a?(Array)
70 | # In case of number or string, this methods return the object
71 | return object if object.is_a?(String) || object.is_a?(Numeric)
72 |
73 | hash={}
74 | object.instance_variables.each do |var|
75 | if !object.instance_variable_get(var).nil?
76 | value = object.instance_variable_get(var)
77 | hashed_value = case value
78 | when Hash then normalize_hash_keys(value, keys_case)
79 | when Array then value.collect{ |item| object_to_hash(item, keys_case) }
80 | when BingAdsApi::DataObject then value.to_hash
81 | else value
82 | end
83 | hash[get_attribute_key(var, keys_case)] = hashed_value
84 | end
85 | end
86 | return hash
87 | end
88 |
89 |
90 | # Public:: Normalize the keys of a hash with the case specified
91 | #
92 | # Author:: jlopexn@neonline.cl
93 | #
94 | # === Parameters
95 | # * +hash+ - Hash to be normalized
96 | # * +keys_case+ - :underscore or :camelcase
97 | #
98 | # === Examples
99 | # normalize_hash_keys({:some_key => value1}, :camelcase)
100 | # # => {"SomeKey" => value1}
101 | #
102 | # normalize_hash_keys({:some_key => value1}, :underscore)
103 | # # => {"some_key" => value1}
104 | #
105 | # Returns:: Hash
106 | def normalize_hash_keys(hash, keys_case)
107 | return hash.inject({}) { |h, (k, v)| h[get_attribute_key(k, keys_case)] = object_to_hash(v, keys_case); h }
108 | end
109 |
110 |
111 | # Internal : Helper method to determinate the key name in the hash for the SOAP request
112 | #
113 | # Author:: jlopezn@neonline.cl
114 | #
115 | # === Parameters
116 | # * attribute - the attribute name
117 | # * keys_case - defines the case for the attribute name.
118 | # ==== keys_case
119 | # * :camelcase - CamelCase
120 | # * :underscore - underscore_case
121 | #
122 | # === Examples
123 | # get_attribute_key("attribute_name", :underscore)
124 | # # => "attribute_name"
125 | #
126 | # get_attribute_key("name", :camelcase)
127 | # # => "AttributeName"
128 | #
129 | # Returns:: String with the attribute name for the key in the hash
130 | def get_attribute_key(attribute, keys_case = :underscore)
131 | if keys_case == :underscore
132 | return attribute.to_s.delete("@").underscore
133 | elsif keys_case == :camelcase
134 | return attribute.to_s.delete("@").camelcase
135 | end
136 | end
137 |
138 |
139 | # Internal : Returns a DateTime as a hash for SOAP requests
140 | #
141 | # Author:: jlopezn@neonline.cl
142 | #
143 | # === Parameters
144 | # * date - DateTime to be hashed
145 | # * keys_case - defines the case for keys in the hash
146 | # ==== keys_case
147 | # * :camelcase - CamelCase
148 | # * :underscore - underscore_case
149 | #
150 | # Returns:: Hash with the :year, :month, :day keys
151 | def date_to_hash(date, keys_case)
152 | {
153 | get_attribute_key("day", keys_case) => date.day,
154 | get_attribute_key("month", keys_case) => date.month,
155 | get_attribute_key("year", keys_case) => date.year
156 | }
157 | end
158 |
159 | end
160 |
161 | end
162 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/keyword_performance_report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public: Reporting service request object for a keyword performance report.
7 | #
8 | # Author:: alex.cavalli@offers.com
9 | #
10 | # === Usage
11 | #
12 | # request = BingAdsApi::KeywordPerformanceReportRequest.new(
13 | # :format => :xml,
14 | # :language => :english,
15 | # :report_name => "Me report",
16 | # :aggregation => :hourly,
17 | # :columns => [:account_name, :account_number, :time_period],
18 | # # The filter is specified as a hash
19 | # :filter => {
20 | # # specifies the Bing expected String value
21 | # :ad_distribution => "Search",
22 | # :ad_type => "Text",
23 | # :bid_match_type => "Exact",
24 | # :delivered_match_type => "Exact",
25 | # # specifies criteria as a snake case symbol
26 | # :device_type => :tablet,
27 | # :keyword_relevance => [3],
28 | # :keywords => ["bing ads"],
29 | # :landing_page_relevance => [2],
30 | # :landing_page_user_experience => [2],
31 | # :language_code => ["EN"],
32 | # :quality_score => [7,8,9,10] },
33 | # :max_rows => 10,
34 | # :scope => {
35 | # :account_ids => [123456, 234567],
36 | # :campaigns => [],
37 | # :ad_groups => [] },
38 | # :sort => []
39 | # # predefined date
40 | # :time => :this_week)
41 | class KeywordPerformanceReportRequest < BingAdsApi::PerformanceReportRequest
42 |
43 | # Valid Columns for this report request
44 | COLUMNS = BingAdsApi::Config.instance.
45 | reporting_constants['keyword_performance_report']['columns']
46 |
47 | # Valid Filters for this report request
48 | FILTERS = BingAdsApi::Config.instance.
49 | reporting_constants['keyword_performance_report']['filter']
50 |
51 | attr_accessor :max_rows, :sort
52 |
53 | # Public : Constructor. Adds a validations for the columns and filter
54 | # attributes
55 | #
56 | # Author:: alex.cavalli@offers.com
57 | #
58 | # === Parameters
59 | # * +attributes+ - Hash with the report request attributes
60 | #
61 | # === Example
62 | #
63 | # request = BingAdsApi::KeywordPerformanceReportRequest.new(
64 | # :format => :xml,
65 | # :language => :english,
66 | # :report_name => "Me report",
67 | # :aggregation => :hourly,
68 | # :columns => [:account_name, :account_number, :time_period],
69 | # # The filter is specified as a hash
70 | # :filter => {
71 | # # specifies the Bing expected String value
72 | # :ad_distribution => "Search",
73 | # :ad_type => "Text",
74 | # :bid_match_type => "Exact",
75 | # :delivered_match_type => "Exact",
76 | # # specifies criteria as a snake case symbol
77 | # :device_type => :tablet,
78 | # :keyword_relevance => [3],
79 | # :landing_page_relevance => [2],
80 | # :landing_page_user_experience => [2],
81 | # :language_code => ["EN"],
82 | # :quality_score => [7,8,9,10] },
83 | # :max_rows => 10,
84 | # :scope => {
85 | # :account_ids => [123456, 234567],
86 | # :campaigns => [],
87 | # :ad_groups => [] },
88 | # :sort => []
89 | # # predefined date
90 | # :time => :this_week)
91 | def initialize(attributes={})
92 | raise Exception.new("Invalid columns") if !valid_columns(COLUMNS, attributes[:columns])
93 | raise Exception.new("Invalid filters") if !valid_filter(FILTERS, attributes[:filter])
94 | super(attributes)
95 | end
96 |
97 |
98 | # Public:: Returns the object as a Hash valid for SOAP requests
99 | #
100 | # Author:: jlopezn@neonline.cl
101 | #
102 | # === Parameters
103 | # * +keys_case+ - case for the hashes keys: underscore or camelcase
104 | #
105 | # Returns:: Hash
106 | def to_hash(keys = :underscore)
107 | hash = super(keys)
108 | hash[get_attribute_key('columns', keys)] =
109 | columns_to_hash(COLUMNS, columns, keys)
110 | hash[get_attribute_key('filter', keys)] =
111 | filter_to_hash(FILTERS, keys)
112 | hash[get_attribute_key('scope', keys)] = scope_to_hash(keys)
113 | hash[get_attribute_key('max_rows', keys)] = max_rows if max_rows
114 | hash[get_attribute_key('sort', keys)] = sort_to_hash(keys) if sort
115 | hash["@xsi:type"] = type_attribute_for_soap
116 | return hash
117 | end
118 |
119 |
120 | private
121 |
122 | # Internal:: Returns the scope attribute as a hash for the SOAP request
123 | #
124 | # Author:: alex.cavalli@offers.com
125 | #
126 | # === Parameters
127 | # * +keys_case+ - case for the hash: underscore or camelcase
128 | #
129 | # Returns:: Hash
130 | def scope_to_hash(keys_case=:underscore)
131 | return {
132 | get_attribute_key('account_ids', keys_case) =>
133 | {"ins0:long" => object_to_hash(scope[:account_ids], keys_case) },
134 | get_attribute_key('ad_group', keys_case) =>
135 | { "AdGroupReportScope" => object_to_hash(scope[:ad_groups], keys_case) },
136 | get_attribute_key('campaigns', keys_case) =>
137 | { "CampaignReportScope" => object_to_hash(scope[:campaigns], keys_case) }
138 | }
139 | end
140 |
141 |
142 | # Internal:: Returns the sort attribute as an array for the SOAP request
143 | #
144 | # Author:: alex.cavalli@offers.com
145 | #
146 | # === Parameters
147 | # * +keys_case+ - case for the hash: underscore or camelcase
148 | #
149 | # Returns:: Array
150 | def sort_to_hash(keys_case=:underscore)
151 | sort.map { |sort_object| object_to_hash(sort_object, keys_case) }
152 | end
153 |
154 |
155 | # Internal:: Returns a string with type attribute for the ReportRequest SOAP tag
156 | #
157 | # Author:: jlopezn@neonline.cl
158 | #
159 | # Returns:: "v9:KeywordPerformanceReportRequest"
160 | def type_attribute_for_soap
161 | return BingAdsApi::ClientProxy::NAMESPACE.to_s + ":" +
162 | BingAdsApi::Config.instance.
163 | reporting_constants['keyword_performance_report']['type']
164 | end
165 |
166 | end
167 |
168 | end
169 |
--------------------------------------------------------------------------------
/spec/reporting_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 |
4 | # Author:: jlopezn@neonline.cl
5 | describe BingAdsApi::Reporting do
6 |
7 | let(:default_options) do
8 | {
9 | environment: :sandbox,
10 | username: "ruby_bing_ads_sbx",
11 | password: "sandbox123",
12 | developer_token: "BBD37VB98",
13 | customer_id: "21025739",
14 | account_id: "8506945"
15 | }
16 | end
17 | let(:service) { BingAdsApi::Reporting.new(default_options) }
18 |
19 | it "should initialize with options" do
20 | new_service = BingAdsApi::Reporting.new(default_options)
21 | expect(new_service).not_to be_nil
22 | end
23 |
24 | it "should submit campaign performance report" do
25 | campaign_id = BingAdsFactory.create_campaign
26 | report_request = BingAdsApi::CampaignPerformanceReportRequest.new(
27 | :format => :xml,
28 | :language => :english,
29 | :report_name => "My Report",
30 | :aggregation => :hourly,
31 | :columns => [
32 | :account_name, :account_number, :time_period,
33 | :campaign_name, :campaign_id, :status, :currency_code,
34 | :impressions, :clicks, :ctr, :average_cpc, :spend,
35 | :conversions, :conversion_rate, :cost_per_conversion
36 | ],
37 | :filter => {
38 | # String as bing expected
39 | :ad_distribution => "Search",
40 | :device_os => "Windows",
41 | # snake case symbol
42 | :device_type => :computer,
43 | # nil criteria
44 | :status => nil
45 | },
46 | :scope => {
47 | :account_ids => default_options[:account_id],
48 | :campaigns => [
49 | {:account_id => default_options[:account_id], :campaign_id => campaign_id}
50 | ]
51 | },
52 | :time => {
53 | :custom_date_range_end => {:day => 31, :month => 12, :year => 2013},
54 | :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
55 | }
56 | )
57 |
58 | report_request_id = nil
59 | expect{
60 | report_request_id = service.submit_generate_report(report_request)
61 | }.not_to raise_error
62 |
63 | expect(report_request_id).not_to be_nil
64 | end
65 |
66 | it "should submit keyword performance report" do
67 | campaign_id = BingAdsFactory.create_campaign
68 | report_request = BingAdsApi::KeywordPerformanceReportRequest.new(
69 | :format => :xml,
70 | :language => :english,
71 | :report_name => "Me report",
72 | :aggregation => :hourly,
73 | :columns => [:account_name, :account_number, :time_period, :keyword, :spend],
74 | # The filter is specified as a hash
75 | :filter => {
76 | # specifies the Bing expected String value
77 | :ad_distribution => "Search",
78 | :ad_type => "Text",
79 | :bid_match_type => "Exact",
80 | :delivered_match_type => "Exact",
81 | # specifies criteria as a snake case symbol
82 | :device_type => :tablet,
83 | :keyword_relevance => [3],
84 | :landing_page_relevance => [2],
85 | :landing_page_user_experience => [2],
86 | :language_code => ["EN"],
87 | :quality_score => [7,8,9,10] },
88 | :max_rows => 10,
89 | :scope => {
90 | :account_ids => default_options[:account_id],
91 | :campaigns => []
92 | },
93 | # predefined date
94 | :time => :this_week
95 | )
96 |
97 | report_request_id = nil
98 | expect{
99 | report_request_id = service.submit_generate_report(report_request)
100 | }.not_to raise_error
101 |
102 | expect(report_request_id).not_to be_nil
103 | end
104 |
105 | it "should submit account performance report" do
106 | report_request = BingAdsApi::AccountPerformanceReportRequest.new(
107 | :format => :xml,
108 | :language => :english,
109 | :report_name => "My Report",
110 | :aggregation => :hourly,
111 | :columns => [
112 | :account_name, :account_number, :time_period,
113 | :currency_code, :impressions, :clicks, :ctr, :average_cpc,
114 | :spend, :conversions, :cost_per_conversion
115 | ],
116 | :filter => {
117 | # String as bing expected
118 | :ad_distribution => "Search",
119 | # snake case symbol
120 | :device_os => :windows,
121 | # nil criteria
122 | :device_type => nil
123 | },
124 | :scope => {:account_ids => default_options[:account_id] },
125 | :time => {
126 | :custom_date_range_end => {:day => 31, :month => 12, :year => 2013},
127 | :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
128 | }
129 | )
130 |
131 | report_request_id = nil
132 | expect{
133 | report_request_id = service.submit_generate_report(report_request)
134 | }.not_to raise_error
135 |
136 | expect(report_request_id).not_to be_nil
137 | end
138 |
139 | it "should initialize report request status" do
140 | report_request_status = BingAdsApi::ReportRequestStatus.new(
141 | :report_download_url => "http://some.url.com",
142 | :status => "Success"
143 | )
144 | expect(report_request_status).not_to be_nil
145 | end
146 |
147 | context "when a report has been requested" do
148 |
149 | before :each do
150 | report_request = BingAdsApi::AccountPerformanceReportRequest.new(
151 | :format => :xml,
152 | :language => :english,
153 | :report_name => "My Report",
154 | :aggregation => :hourly,
155 | :columns => [
156 | :account_name, :account_number, :time_period,
157 | :currency_code, :impressions, :clicks, :ctr, :average_cpc,
158 | :spend, :conversions, :cost_per_conversion
159 | ],
160 | :filter => {
161 | # String as bing expected
162 | :ad_distribution => "Search",
163 | # snake case symbol
164 | :device_os => :windows,
165 | # nil criteria
166 | :device_type => nil
167 | },
168 | :scope => {:account_ids => default_options[:account_id] },
169 | :time => {
170 | :custom_date_range_end => {:day => 31, :month => 12, :year => 2013},
171 | :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
172 | }
173 | )
174 |
175 | @report_request_id = service.submit_generate_report(report_request)
176 | end
177 |
178 | it "should successfully poll generate report" do
179 | report_request_status = nil
180 | expect{
181 | report_request_status = service.poll_generate_report(@report_request_id)
182 | }.not_to raise_error
183 |
184 | expect(report_request_status).not_to be_nil
185 |
186 | expect(report_request_status.error?).to eq(false)
187 | end
188 |
189 | end
190 | end
191 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/account_performance_report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object for all report requests.
7 | # Do not instantiate this object. Instead, you may instantiate one
8 | # of the following report request objects which derives from this object to request a report.
9 | #
10 | # Reference: http://msdn.microsoft.com/en-us/library/bing-ads-reporting-bing-ads-reportrequest.aspx
11 | #
12 | # Author:: jlopezn@neonline.cl
13 | #
14 | # === Usage
15 | #
16 | # request = BingAdsApi::AccountPerformanceReportRequest.new(
17 | # :format => :xml,
18 | # :language => :english,
19 | # :report_name => "Me report",
20 | # :aggregation => :hourly,
21 | # :columns => [:account_name, :account_number, :time_period],
22 | # # The filter is specified as a hash
23 | # :filter => {
24 | # :ad_distribution => :search,
25 | # :device_os => :android,
26 | # :device_type => :tablet,
27 | # :status => :submited },
28 | # :scope => {
29 | # :account_ids => [123456, 234567],
30 | # :campaigns => [] },
31 | # # predefined date
32 | # :time => :this_week)
33 | #
34 | # request2 = BingAdsApi::AccountPerformanceReportRequest.new(
35 | # :format => :csv,
36 | # :language => :french,
37 | # :report_name => "Me report",
38 | # :aggregation => :daily,
39 | # :columns => [:account_name, :account_number, :time_period],
40 | # # no filter is specified
41 | # :scope => {
42 | # :account_ids => [123456, 234567],
43 | # :campaigns => [] },
44 | # # Custom date range
45 | # :time => {
46 | # :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
47 | # :custom_date_range_end => {:day => 12, :month => 12, :year => 2013} }
48 | # )
49 | class AccountPerformanceReportRequest < BingAdsApi::PerformanceReportRequest
50 |
51 | # Valid Columns for this report request
52 | COLUMNS = BingAdsApi::Config.instance.
53 | reporting_constants['account_performance_report']['columns']
54 |
55 | # Valid Filters for this report request
56 | FILTERS = BingAdsApi::Config.instance.
57 | reporting_constants['account_performance_report']['filter']
58 |
59 |
60 | # Public : Constructor. Adds a validations for the columns, filter
61 | # and scope attributes
62 | #
63 | # Author:: jlopezn@neonline.cl
64 | #
65 | # === Parameters
66 | # attributes - Hash with the report request attributes
67 | #
68 | # === Example
69 | #
70 | # request = BingAdsApi::AccountPerformanceReportRequest.new(
71 | # :format => :xml,
72 | # :language => :english,
73 | # :report_name => "Me report",
74 | # :aggregation => :hourly,
75 | # :columns => [:account_name, :account_number, :time_period],
76 | # # The filter is specified as a hash
77 | # :filter => {
78 | # :ad_distribution => :search,
79 | # :device_os => :android,
80 | # :device_type => :tablet,
81 | # :status => :submited },
82 | # :scope => {
83 | # :account_ids => [123456, 234567],
84 | # :campaigns => [] },
85 | # # predefined date
86 | # :time => :this_week)
87 | #
88 | # request2 = BingAdsApi::AccountPerformanceReportRequest.new(
89 | # :format => :csv,
90 | # :language => :french,
91 | # :report_name => "Me report",
92 | # :aggregation => :daily,
93 | # :columns => [:account_name, :account_number, :time_period],
94 | # # no filter is specified
95 | # :scope => {
96 | # :account_ids => [123456, 234567],
97 | # :campaigns => [] },
98 | # # Custom date range
99 | # :time => {
100 | # :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
101 | # :custom_date_range_end => {:day => 12, :month => 12, :year => 2013} }
102 | # )
103 | def initialize(attributes={})
104 | raise "Invalid columns" if !valid_columns(COLUMNS, attributes[:columns])
105 | raise "Invalid filters" if !valid_filter(FILTERS, attributes[:filter])
106 | raise "Invalid scope" if !valid_scope(attributes[:scope])
107 | super(attributes)
108 | end
109 |
110 | # Public:: Returns the object as a Hash valid for SOAP requests
111 | #
112 | # Author:: jlopezn@neonline.cl
113 | #
114 | # === Parameters
115 | # * +keys_case+ - case for the hashes keys: underscore or camelcase
116 | #
117 | # Returns:: Hash
118 | def to_hash(keys = :underscore)
119 | hash = super(keys)
120 | hash[get_attribute_key('columns', keys)] =
121 | columns_to_hash(COLUMNS, columns, keys)
122 | hash[get_attribute_key('filter', keys)] =
123 | filter_to_hash(FILTERS, keys)
124 | hash[get_attribute_key('scope', keys)] = scope_to_hash(keys)
125 | hash["@xsi:type"] = type_attribute_for_soap
126 | return hash
127 | end
128 |
129 | private
130 |
131 |
132 |
133 | # Internal:: Validates the scope attribute given in the constructor
134 | #
135 | # Author:: jlopezn@neonline.cl
136 | #
137 | # === Parameters
138 | # * +scope+ - value for the 'scope' key in the has initializer
139 | #
140 | # Returns:: true if the scope specification is valid. Raises Exception otherwise
141 | #
142 | # Raises:: Exception if the scope is not valid
143 | def valid_scope(scope)
144 | raise Exception.new("Invalid scope: no account_ids key") if !scope.key?(:account_ids)
145 | return true
146 | end
147 |
148 |
149 | # Internal:: Returns the scope attribute as a hash for the SOAP request
150 | #
151 | # Author:: jlopezn@neonline.cl
152 | #
153 | # === Parameters
154 | # * +keys_case+ - case for the hash: underscore or camelcase
155 | #
156 | # Returns:: Hash
157 | def scope_to_hash(keys_case=:underscore)
158 | return { get_attribute_key('account_ids', keys_case) =>
159 | {"ins0:long" => object_to_hash(scope[:account_ids], keys_case)} }
160 | end
161 |
162 |
163 | # Internal:: Returns a string with type attribute for the ReportRequest SOAP tag
164 | #
165 | # Author:: jlopezn@neonline.cl
166 | #
167 | # Returns:: "v9:AccountPerformanceReportRequest"
168 | def type_attribute_for_soap
169 | return BingAdsApi::ClientProxy::NAMESPACE.to_s + ":" +
170 | BingAdsApi::Config.instance.
171 | reporting_constants['account_performance_report']['type']
172 | end
173 |
174 | end
175 |
176 | end
177 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/budget_summary_report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public: Reporting service request object for a keyword performance report.
7 | #
8 | # Author:: alex.cavalli@offers.com
9 | #
10 | # === Usage
11 | #
12 | # request = BingAdsApi::KeywordPerformanceReportRequest.new(
13 | # :format => :xml,
14 | # :language => :english,
15 | # :report_name => "Me report",
16 | # :aggregation => :hourly,
17 | # :columns => [:account_name, :account_number, :time_period],
18 | # # The filter is specified as a hash
19 | # :filter => {
20 | # # specifies the Bing expected String value
21 | # :ad_distribution => "Search",
22 | # :ad_type => "Text",
23 | # :bid_match_type => "Exact",
24 | # :delivered_match_type => "Exact",
25 | # # specifies criteria as a snake case symbol
26 | # :device_type => :tablet,
27 | # :keyword_relevance => [3],
28 | # :keywords => ["bing ads"],
29 | # :landing_page_relevance => [2],
30 | # :landing_page_user_experience => [2],
31 | # :language_code => ["EN"],
32 | # :quality_score => [7,8,9,10] },
33 | # :max_rows => 10,
34 | # :scope => {
35 | # :account_ids => [123456, 234567],
36 | # :campaigns => [],
37 | # :ad_groups => [] },
38 | # :sort => []
39 | # # predefined date
40 | # :time => :this_week)
41 | class BudgetSummaryReportRequest < BingAdsApi::ReportRequest
42 |
43 | # Adds helper methods to time attribute
44 | include BingAdsApi::Helpers::TimeHelper
45 |
46 | # Adds helper methods to column attribute
47 | include BingAdsApi::Helpers::ColumnHelper
48 |
49 | # Adds helper methods to filter attribute
50 | include BingAdsApi::Helpers::FilterHelper
51 |
52 |
53 | # Valid Columns for this report request
54 | COLUMNS = BingAdsApi::Config.instance.
55 | reporting_constants['budget_summary_report']['columns']
56 |
57 | # Valid Filters for this report request
58 | FILTERS = BingAdsApi::Config.instance.
59 | reporting_constants['budget_summary_report']['filter']
60 |
61 | attr_accessor :max_rows, :sort, :columns, :filter, :scope, :time
62 |
63 |
64 | # Public : Constructor. Adds a validations for the columns and filter
65 | # attributes
66 | #
67 | # Author:: alex.cavalli@offers.com
68 | #
69 | # === Parameters
70 | # * +attributes+ - Hash with the report request attributes
71 | #
72 | # === Example
73 | #
74 | # request = BingAdsApi::KeywordPerformanceReportRequest.new(
75 | # :format => :xml,
76 | # :language => :english,
77 | # :report_name => "Me report",
78 | # :aggregation => :hourly,
79 | # :columns => [:account_name, :account_number, :time_period],
80 | # # The filter is specified as a hash
81 | # :filter => {
82 | # # specifies the Bing expected String value
83 | # :ad_distribution => "Search",
84 | # :ad_type => "Text",
85 | # :bid_match_type => "Exact",
86 | # :delivered_match_type => "Exact",
87 | # # specifies criteria as a snake case symbol
88 | # :device_type => :tablet,
89 | # :keyword_relevance => [3],
90 | # :landing_page_relevance => [2],
91 | # :landing_page_user_experience => [2],
92 | # :language_code => ["EN"],
93 | # :quality_score => [7,8,9,10] },
94 | # :max_rows => 10,
95 | # :scope => {
96 | # :account_ids => [123456, 234567],
97 | # :campaigns => [],
98 | # :ad_groups => [] },
99 | # :sort => []
100 | # # predefined date
101 | # :time => :this_week)
102 | def initialize(attributes={})
103 | raise Exception.new("Invalid columns") if !valid_columns(COLUMNS, attributes[:columns])
104 | raise Exception.new("Invalid time") if !valid_time(attributes[:time])
105 | super(attributes)
106 | end
107 |
108 |
109 | # Public:: Returns the object as a Hash valid for SOAP requests
110 | #
111 | # Author:: jlopezn@neonline.cl
112 | #
113 | # === Parameters
114 | # * +keys_case+ - case for the hashes keys: underscore or camelcase
115 | #
116 | # Returns:: Hash
117 | def to_hash(keys = :underscore)
118 | hash = super(keys)
119 | hash[get_attribute_key('columns', keys)] =
120 | columns_to_hash(COLUMNS, columns, keys)
121 | hash[get_attribute_key('filter', keys)] =
122 | filter_to_hash(FILTERS, keys)
123 | hash[get_attribute_key('scope', keys)] = scope_to_hash(keys)
124 | hash[get_attribute_key('max_rows', keys)] = max_rows if max_rows
125 | hash[get_attribute_key('sort', keys)] = sort_to_hash(keys) if sort
126 | hash["@xsi:type"] = type_attribute_for_soap
127 | hash[get_attribute_key('time', keys)] = time_to_hash(keys)
128 | return hash
129 | end
130 |
131 |
132 | private
133 |
134 | # Internal:: Returns the scope attribute as a hash for the SOAP request
135 | #
136 | # Author:: alex.cavalli@offers.com
137 | #
138 | # === Parameters
139 | # * +keys_case+ - case for the hash: underscore or camelcase
140 | #
141 | # Returns:: Hash
142 | def scope_to_hash(keys_case=:underscore)
143 | return {
144 | get_attribute_key('account_ids', keys_case) =>
145 | {"ins0:long" => object_to_hash(scope[:account_ids], keys_case) },
146 | get_attribute_key('campaigns', keys_case) =>
147 | { "CampaignReportScope" => object_to_hash(scope[:campaigns], keys_case) }
148 | }
149 | end
150 |
151 |
152 | # Internal:: Returns the sort attribute as an array for the SOAP request
153 | #
154 | # Author:: alex.cavalli@offers.com
155 | #
156 | # === Parameters
157 | # * +keys_case+ - case for the hash: underscore or camelcase
158 | #
159 | # Returns:: Array
160 | def sort_to_hash(keys_case=:underscore)
161 | sort.map { |sort_object| object_to_hash(sort_object, keys_case) }
162 | end
163 |
164 |
165 | # Internal:: Returns a string with type attribute for the ReportRequest SOAP tag
166 | #
167 | # Author:: jlopezn@neonline.cl
168 | #
169 | # Returns:: "v9:KeywordPerformanceReportRequest"
170 | def type_attribute_for_soap
171 | return BingAdsApi::ClientProxy::NAMESPACE.to_s + ":" +
172 | BingAdsApi::Config.instance.
173 | reporting_constants['budget_summary_report']['type']
174 | end
175 |
176 | end
177 |
178 | end
179 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/service.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | # Public : Base class for service object
6 | #
7 | # Author:: jlopezn@neonline.cl
8 | #
9 | class Service
10 |
11 | attr_accessor :client_proxy, :environment, :max_retry_attempts
12 |
13 | # Default logger for services
14 | LOGGER = Logger.new(STDOUT)
15 |
16 |
17 | # Public : Constructor
18 | #
19 | # Author:: jlopezn@neonline.cl
20 | #
21 | # === Parameters
22 | # * +options+ - Hash with autentication and environment settings
23 | #
24 | # === Options
25 | # * environment - +:production+ or +:sandbox+
26 | # * username - Bing Ads username
27 | # * passwrod - Bing Ads user's sign-in password
28 | # * developer_token - client application's developer access token
29 | # * customer_id - identifier for the customer that owns the account
30 | # * account_id - identifier of the account that own the entities in the request
31 | # * proxy - Hash with any Client Proxy additional options (such as header, logger or enconding)
32 | #
33 | # === Examples
34 | # service = BingAdsApi::Service.new(
35 | # :environment => :sandbox,
36 | # :username => 'username',
37 | # :password => 'pass',
38 | # :developer_token => 'SOME_TOKEN',
39 | # :account_id => 123456,
40 | # :customer_id => 654321,
41 | # :proxy => {:logger => Rails.logger}
42 | # )
43 | # # =>
44 | def initialize(options={})
45 |
46 | # Service Environment
47 | self.environment = options[:environment]
48 |
49 | # ClientProxy settings
50 | clientProxySettings = {
51 | :authentication_token => options[:authentication_token],
52 | :username => options[:username],
53 | :password => options[:password],
54 | :developer_token => options[:developer_token],
55 | :account_id => options[:account_id],
56 | :customer_id => options[:customer_id],
57 | :wsdl_url => options[:wsdl] || solve_wsdl_url
58 | }
59 |
60 | # Additionsl ClientProxy settings
61 | clientProxySettings[:proxy] = options[:proxy] if options[:proxy]
62 |
63 | # ClientProxy creation
64 | self.client_proxy = BingAdsApi::ClientProxy.new(clientProxySettings)
65 |
66 | self.max_retry_attempts = options[:max_retry_attempts] || 0
67 | end
68 |
69 |
70 | # Public : This is a utility wrapper for calling services into the
71 | # +ClientProxy+. This methods handle all the +Savon::Client+ Exceptions
72 | # and returns a Hash with the call response
73 | #
74 | # Author:: jlopezn@neonline.cl
75 | #
76 | # === Parameters
77 | # +operation+ - name of the operation to be called
78 | # +message+ - hash with the parameters to the operation
79 | #
80 | # === Examples
81 | # service.call(:some_operation, {key: value})
82 | # # =>
83 | #
84 | # Returns:: Hash with the result of the service call
85 | # Raises:: ServiceError if the SOAP call, the ClientProxy fails or the response is invalid
86 | def call(operation, message, &block)
87 | retries_made = 0
88 | raise "You must provide an operation" if operation.nil?
89 | begin
90 | LOGGER.debug "BingAdsApi Service"
91 | LOGGER.debug " Calling #{operation.to_s}"
92 | LOGGER.debug " Message: #{message}"
93 | response = self.client_proxy.call(operation.to_sym,
94 | message: message)
95 | LOGGER.debug "response header:"
96 | LOGGER.debug "\t#{response.header}"
97 |
98 | LOGGER.info "Operation #{operation.to_s} call success"
99 | return response.hash
100 | rescue Savon::SOAPFault => error
101 | LOGGER.error "SOAP Error calling #{operation.to_s}: #{error.http.code}"
102 | fault_detail = error.to_hash[:fault][:detail]
103 | if fault_detail.key?(:api_fault_detail)
104 | api_fault_detail = BingAdsApi::ApiFaultDetail.new(fault_detail[:api_fault_detail])
105 | raise BingAdsApi::ApiException.new(
106 | api_fault_detail, "SOAP Error calling #{operation.to_s}")
107 | elsif fault_detail.key?(:ad_api_fault_detail)
108 | ad_api_fault_detail = BingAdsApi::AdApiFaultDetail.new(fault_detail[:ad_api_fault_detail])
109 | raise BingAdsApi::ApiException.new(
110 | ad_api_fault_detail, "SOAP Error calling #{operation.to_s}")
111 | else
112 | raise
113 | end
114 | rescue Savon::HTTPError => error
115 | LOGGER.error "Http Error calling #{operation.to_s}: #{error.http.code}"
116 | raise
117 | rescue Savon::InvalidResponseError => error
118 | LOGGER.error "Invalid server reponse calling #{operation.to_s}"
119 | raise
120 | rescue
121 | # for any other exceptions, retry with an exponential backoff
122 | if retries_made < max_retry_attempts
123 | sleep(2**(retries_made))
124 | retries_made += 1
125 | retry
126 | else
127 | raise
128 | end
129 | end
130 | end
131 |
132 |
133 | # Public : Extracts the actual response from the entire response hash.
134 | # For example, if you specify 'AddCampaigns', this method will return
135 | # the content of 'AddCampaignsResponse' tag as a Hash
136 | #
137 | # Author:: jlopezn@neonline.cl
138 | #
139 | # === Parameters
140 | # response - The complete response hash received from a Operation call
141 | # method - Name of the method of with the 'reponse' tag is require
142 | #
143 | # === Examples
144 | # service.get_response_hash(Hash, 'add_campaigns')
145 | # # => Hash
146 | #
147 | # Returns:: Hash with the inner structure of the method response hash
148 | # Raises:: exception
149 | def get_response_hash(response, method)
150 | return response[:envelope][:body]["#{method}_response".to_sym]
151 | end
152 |
153 | private
154 |
155 | # Private : This method must be overriden by specific services.
156 | # Returns:: the service name
157 | #
158 | # Author:: jlopezn@neonline.cl
159 | #
160 | # Examples
161 | # get_service_name
162 | # # => "service_name"
163 | #
164 | # Returns:: String with the service name
165 | # Raises:: exception if the specific Service class hasn't overriden this method
166 | def get_service_name
167 | raise "Should return the a service name from config.wsdl keys"
168 | end
169 |
170 |
171 | # Private : Solves the service WSDL URL based on his service name
172 | # and environment values
173 | #
174 | # Author:: jlopezn@neonline.cl
175 | #
176 | # Examples
177 | # solve_wsdl_url
178 | # # => "https://bing.wsdl.url.com"
179 | #
180 | # Returns:: String with the Service url
181 | def solve_wsdl_url
182 | config = BingAdsApi::Config.instance
183 | return config.service_wsdl(environment, get_service_name)
184 | end
185 |
186 | end
187 |
188 | end
189 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/data/reporting/campaign_performance_report_request.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 |
3 | module BingAdsApi
4 |
5 | ##
6 | # Public : Defines the base object for all report requests.
7 | # Do not instantiate this object. Instead, you may instantiate one
8 | # of the following report request objects which derives from this object to request a report.
9 | #
10 | # Reference: http://msdn.microsoft.com/en-us/library/bing-ads-reporting-bing-ads-reportrequest.aspx
11 | #
12 | # Author:: jlopezn@neonline.cl
13 | #
14 | # === Usage
15 | #
16 | # request = BingAdsApi::CampaignPerformanceReportRequest.new(
17 | # :format => :xml,
18 | # :language => :english,
19 | # :report_name => "Me report",
20 | # :aggregation => :hourly,
21 | # :columns => [:account_name, :account_number, :time_period],
22 | # # The filter is specified as a hash
23 | # :filter => {
24 | # # specifies the Bing expected String value
25 | # :ad_distribution => "Search",
26 | # # specifies criteria as a snake case symbol
27 | # :device_os => :android,
28 | # :device_type => :tablet,
29 | # # criteria nil is similar to not specify it at all
30 | # :status => nil },
31 | # :scope => {
32 | # :account_ids => [123456, 234567],
33 | # :campaigns => [] },
34 | # # predefined date
35 | # :time => :this_week)
36 | #
37 | # request2 = BingAdsApi::CampaignPerformanceReportRequest.new(
38 | # :format => :csv,
39 | # :language => :french,
40 | # :report_name => "Me report",
41 | # :aggregation => :daily,
42 | # :columns => [:account_name, :account_number, :time_period],
43 | # # no filter is specified
44 | # :scope => {
45 | # :account_ids => [123456, 234567],
46 | # :campaigns => [] },
47 | # # Custom date range
48 | # :time => {
49 | # :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
50 | # :custom_date_range_end => {:day => 12, :month => 12, :year => 2013} }
51 | # )
52 | class CampaignPerformanceReportRequest < BingAdsApi::PerformanceReportRequest
53 |
54 | # Valid Columns for this report request
55 | COLUMNS = BingAdsApi::Config.instance.
56 | reporting_constants['campaign_performance_report']['columns']
57 |
58 | # Valid Filters for this report request
59 | FILTERS = BingAdsApi::Config.instance.
60 | reporting_constants['campaign_performance_report']['filter']
61 |
62 | # Public : Constructor. Adds a validations for the columns, filter
63 | # and scope attributes
64 | #
65 | # Author:: jlopezn@neonline.cl
66 | #
67 | # === Parameters
68 | # * +attributes+ - Hash with the report request attributes
69 | #
70 | # === Example
71 | #
72 | # request = BingAdsApi::CampaignPerformanceReportRequest.new(
73 | # :format => :xml,
74 | # :language => :english,
75 | # :report_name => "Me report",
76 | # :aggregation => :hourly,
77 | # :columns => [:account_name, :account_number, :time_period],
78 | # # The filter is specified as a hash
79 | # :filter => {
80 | # # specifies the Bing expected String value
81 | # :ad_distribution => "Search",
82 | # # specifies criteria as a snake case symbol
83 | # :device_os => :android,
84 | # :device_type => :tablet,
85 | # # criteria nil is similar to not specify it at all
86 | # :status => nil },
87 | # :scope => {
88 | # :account_ids => [123456, 234567],
89 | # :campaigns => [] },
90 | # # predefined date
91 | # :time => :this_week)
92 | #
93 | # request2 = BingAdsApi::CampaignPerformanceReportRequest.new(
94 | # :format => :csv,
95 | # :language => :french,
96 | # :report_name => "Me report",
97 | # :aggregation => :daily,
98 | # :columns => [:account_name, :account_number, :time_period],
99 | # # no filter is specified
100 | # :scope => {
101 | # :account_ids => [123456, 234567],
102 | # :campaigns => [] },
103 | # # Custom date range
104 | # :time => {
105 | # :custom_date_range_start => {:day => 1, :month => 12, :year => 2013},
106 | # :custom_date_range_end => {:day => 12, :month => 12, :year => 2013} }
107 | # )
108 | def initialize(attributes={})
109 | raise Exception.new("Invalid columns") if !valid_columns(COLUMNS, attributes[:columns])
110 | raise Exception.new("Invalid filters") if !valid_filter(FILTERS, attributes[:filter])
111 | raise Exception.new("Invalid scope") if !valid_scope(attributes[:scope])
112 | super(attributes)
113 | end
114 |
115 |
116 | # Public:: Returns the object as a Hash valid for SOAP requests
117 | #
118 | # Author:: jlopezn@neonline.cl
119 | #
120 | # === Parameters
121 | # * +keys_case+ - case for the hashes keys: underscore or camelcase
122 | #
123 | # Returns:: Hash
124 | def to_hash(keys = :underscore)
125 | hash = super(keys)
126 | hash[get_attribute_key('columns', keys)] =
127 | columns_to_hash(COLUMNS, columns, keys)
128 | hash[get_attribute_key('filter', keys)] =
129 | filter_to_hash(FILTERS, keys)
130 | hash[get_attribute_key('scope', keys)] = scope_to_hash(keys)
131 | hash["@xsi:type"] = type_attribute_for_soap
132 | return hash
133 | end
134 |
135 |
136 | private
137 |
138 | # Internal:: Validates the scope attribute given in the constructor
139 | #
140 | # Author:: jlopezn@neonline.cl
141 | #
142 | # === Parameters
143 | # * +scope+ - value for the 'scope' key in the has initializer
144 | #
145 | # Returns:: true if the scope specification is valid. Raises Exception otherwise
146 | #
147 | # Raises:: Exception if the scope is not valid
148 | def valid_scope(scope)
149 | raise Exception.new("Invalid scope: no account_ids key") if !scope.key?(:account_ids)
150 | raise Exception.new("Invalid scope: no campaigns key") if !scope.key?(:campaigns)
151 | return true
152 | end
153 |
154 |
155 | # Internal:: Returns the scope attribute as a hash for the SOAP request
156 | #
157 | # Author:: jlopezn@neonline.cl
158 | #
159 | # === Parameters
160 | # * +keys_case+ - case for the hash: underscore or camelcase
161 | #
162 | # Returns:: Hash
163 | def scope_to_hash(keys_case=:underscore)
164 | return {
165 | get_attribute_key('account_ids', keys_case) => {"ins0:long" => object_to_hash(scope[:account_ids], keys_case)},
166 | get_attribute_key('campaigns', keys_case) =>
167 | { "CampaignReportScope" => object_to_hash(scope[:campaigns], keys_case) }
168 | }
169 | end
170 |
171 |
172 | # Internal:: Returns a string with type attribute for the ReportRequest SOAP tag
173 | #
174 | # Author:: jlopezn@neonline.cl
175 | #
176 | # Returns:: "v9:CampaignPerformanceReportRequest"
177 | def type_attribute_for_soap
178 | return BingAdsApi::ClientProxy::NAMESPACE.to_s + ":" +
179 | BingAdsApi::Config.instance.
180 | reporting_constants['campaign_performance_report']['type']
181 | end
182 |
183 | end
184 |
185 | end
186 |
--------------------------------------------------------------------------------
/lib/locales/es.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | es-CL:
5 |
6 | bing:
7 |
8 | constants:
9 |
10 | common:
11 | time_zones:
12 | abu_dhabi_muscat: 'Abu Dhabi y Muscat'
13 | adelaide: 'Adelaide, Australia'
14 | alaska: 'Alaska'
15 | almaty_novosibirsk: 'Almaty y Novosibirsk'
16 | amsterdam_berlin_bern_rome_stockholm_vienna: 'Amsterdam, Berlin, Bern, Rome, Stockholm, y Vienna'
17 | arizona: 'Arizona'
18 | astana_dhaka: 'Astana y Dhaka'
19 | athens_buckarest_istanbul: 'Athens, Istanbul, Beirut, y Minsk'
20 | atlantic_time_canada: 'Atlantic Time y Canada'
21 | auckland_wellington: 'Auckland y Wellington, New Zealand'
22 | azores: 'Azores'
23 | baghdad: 'Baghdad'
24 | baku_tbilisi_yerevan: 'Baku, Tbilisi, y Yerevan'
25 | bangkok_hanoi_jakarta: 'Bangkok, Hanoi, y Jakarta'
26 | beijing_chongqing_hong_kong_urumqi: 'Beijing, Chongqing, Hong Kong, y Urumqi'
27 | belgrade_bratislava_budapest_ljubljana_prague: 'Belgrade, Bratislava, Budapest, Ljubljana, y Prague'
28 | bogota_lima_quito: 'Bogota, Lima, y Quito'
29 | brasilia: 'Brasilia'
30 | brisbane: 'Brisbane'
31 | brussels_copenhagen_madrid_paris: 'Brussels, Copenhagen, Madrid, y Paris'
32 | bucharest: 'Bucharest'
33 | buenos_aires_georgetown: 'Buenos Aires y Georgetown'
34 | cairo: 'Cairo'
35 | canberra_melbourne_sydney: 'Canberra, Melbourne, y Sydney'
36 | cape_verde_island: 'Cape Verde Island'
37 | caracas_la_paz: 'Caracas y La Paz (Bolivia)'
38 | casablanca_monrovia: 'Casablanca y Monrovia'
39 | central_america: 'Central America'
40 | central_time_u_s_canada: 'Central Time U.S. y Canada'
41 | chennai_kolkata_mumbai_new_delhi: 'Chennai, Kolkata, Mumbai, y New Delhi'
42 | chihuahua_la_paz_mazatlan: 'Chihuahua, La Paz (Mexico), y Mazatlan'
43 | darwin: 'Darwin'
44 | eastern_time_u_s_canada: 'Eastern Time U.S. y Canada'
45 | ekaterinburg: 'Ekaterinburg'
46 | fiji_kamchatka_marshall_island: 'Fiji, Kamchatka, y Marshall Island'
47 | greenland: 'Greenland'
48 | greenwich_mean_time_dublin_edinburgh_lisbon_london: 'Greenwich Mean Time, Dublin, Edinburgh, Lisbon, y London'
49 | guadalajara_mexico_city_monterrey: 'Guadalajara, Mexico City, y Monterrey'
50 | guam_port_moresby: 'Guam y Port Moresby'
51 | harare_pretoria: 'Harare y Pretoria'
52 | hawaii: 'Hawaii'
53 | helsinki_kyiv_riga_sofia_tallinn_vilnius: 'Helsinki, Kyiv, Riga, Sofia, Tallinn, y Vilnius'
54 | hobart: 'Hobart'
55 | indiana_east: 'Indiana (East)'
56 | international_date_line_west: 'International Date Line (West)'
57 | irkutsk_ulaan_bataar: 'Irkutsk y Ulaanbaatar'
58 | islandamabad_karachi_tashkent: 'Islamabad, Karachi, y Tashkent'
59 | jerusalem: 'Jerusalem'
60 | kabul: 'Kabul'
61 | kathmandu: 'Kathmandu'
62 | krasnoyarsk: 'Krasnoyarsk'
63 | kuala_lumpur_singapore: 'Kuala Lumpur y Singapore'
64 | kuwait_riyadh: 'Kuwait y Riyadh'
65 | magadan_solomon_island_new_caledonia: 'Magadan, Solomon Islands, y New Caledonia'
66 | mid_atlantic: 'Mid-Atlantic'
67 | midway_islandand_samoa: 'Midway Island y Samoa'
68 | moscow_st_petersburg_volgograd: 'Moscow, St Petersburg, y Volgograd'
69 | mountain_time_u_s_canada: 'Mountain Time U.S. y Canada'
70 | nairobi: 'Nairobi'
71 | newfoundland: 'Newfoundland'
72 | nukualofa: 'Nuku'alofa'
73 | osaka_sapporo_tokyo: 'Osaka, Sapporo, y Tokyo'
74 | pacific_time_u_s_canada_tijuana: 'Pacific Time, U.S. y Canada, y Tijuana'
75 | perth: 'Perth'
76 | rangoon: 'Rangoon'
77 | santiago: 'Santiago'
78 | sarajevo_skopje_warsaw_zagreb: 'Sarajevo, Skopie, Warsaw, y Zagreb'
79 | saskatchewan: 'Saskatchewan'
80 | seoul: 'Seoul'
81 | sri_jayawardenepura: 'Sri Jayawardenepura'
82 | taipei: 'Taipei'
83 | tehran: 'Tehran'
84 | vladivostok: 'Vladivostok'
85 | west_central_africa: 'West Central Africa'
86 | yakutsk: 'Yakutsk'
87 | ad_languages:
88 | danish: 'Danish'
89 | dutch: 'Dutch'
90 | english: 'English'
91 | finnish: 'Finnish'
92 | french: 'French'
93 | german: 'German'
94 | italian: 'Italian'
95 | norwegian: 'Norwegian'
96 | portuguese: 'Portuguese'
97 | spanish: 'Spanish'
98 | swedish: 'Swedish'
99 | traditional_chinese: 'Traditional Chinese'
100 | codes:
101 | danish: 'DA'
102 | dutch: 'NL'
103 | english: 'EN'
104 | finnish: 'FI'
105 | french: 'FR'
106 | german: 'DE'
107 | italian: 'IT'
108 | norwegian: 'NO'
109 | portuguese: 'PT'
110 | spanish: 'ES'
111 | swedish: 'SV'
112 | traditional_chinese: 'ZH'
113 | market_country:
114 | danish: 'Denmark'
115 | dutch: 'Netherlands'
116 | english: 'Australia Canada India Indonesia Ireland Malaysia New Zealand Philippines Singapore Thailand United Kingdom United States Vietnam'
117 | finnish: 'Finland '
118 | french: 'Belgium Canada France'
119 | german: 'Austria Germany Switzerland'
120 | italian: 'Italy'
121 | norwegian: 'Norway'
122 | portuguese: 'Brazil'
123 | spanish: 'Argentina Chile Colombia Mexico Peru Spain Venezuela'
124 | swedish: 'Sweden'
125 | traditional_chinese: 'Hong Kong Taiwan'
126 |
127 | campaign_management:
128 | budget_limit_type:
129 | monthly_budget_spend_until_depleted: 'MonthlyBudgetSpendUntilDepleted'
130 | daily_budget_accelerated: 'DailyBudgetAccelerated'
131 | daily_budget_standard: 'DailyBudgetStandard'
132 | campaign_status:
133 | active: 'Activa'
134 | paused: 'Pausada'
135 | budget_paused: 'Pausada por presupuesto'
136 | budget_and_manual_paused: 'Pausada por presupuesto y manual'
137 | deleted: 'Deleted'
138 | ad_distribution:
139 | search: 'Búsqueda'
140 | content: 'Contenido'
141 | ad_rotation_type:
142 | optimize_for_clicks: 'Optimizado por clicks'
143 | rotate_ads_evenly: 'Rotar anuncios parejo'
144 | bidding_model:
145 | keyword: 'Keywords'
146 | site_placement: 'Posición en sitio'
147 | pricing_model:
148 | cpc: 'Cpc'
149 | cpm: 'Cpm'
150 | ad_group_status:
151 | draft: 'Borrador'
152 | active: 'Activo'
153 | paused: 'Pausado'
154 | deleted: 'Eliminado'
155 | ad_status:
156 | inactive: 'Inactivo'
157 | Active: 'Activo'
158 | paused: 'Pausado'
159 | deleted: 'Eliminado'
160 | ad_type:
161 | text: 'Texto'
162 | image: 'Imágen'
163 | mobile: 'Mobil'
164 | rich_search: 'Búsqueda enriquecida'
165 | product: 'Producto'
166 | keyword_editorial_status:
167 | active: 'Activo'
168 | disapproved: 'Desaprovado'
169 | inactive: 'Inactivo'
170 | keyword_status:
171 | active: 'Activo'
172 | paused: 'Pausado'
173 | deleted: 'Borrado'
174 | inactive: 'Inactivo'
175 |
--------------------------------------------------------------------------------
/lib/bing-ads-api/service/bulk.rb:
--------------------------------------------------------------------------------
1 | require 'rest_client'
2 |
3 | # -*- encoding : utf-8 -*-
4 | module BingAdsApi
5 |
6 | # Public: This class represents the Bulk Services defined in the Bing Ads API
7 | # to request and download and upload bulk account and campaign data
8 | #
9 | # Author:: alex.cavalli@offers.com
10 | #
11 | # Examples
12 | # options = {
13 | # :environment => :sandbox,
14 | # :username => "username",
15 | # :password => "pass",
16 | # :developer_token => "SOME_TOKEN",
17 | # :customer_id => "1234567",
18 | # :account_id => "9876543" }
19 | # service = BingAdsApi::Bulk.new(options)
20 | class Bulk < BingAdsApi::Service
21 |
22 | # Valid download file types for bulk API
23 | DOWNLOAD_FILE_TYPES = BingAdsApi::Config.instance.
24 | bulk_constants['download_file_type']
25 |
26 | RESPONSE_MODES = BingAdsApi::Config.instance.
27 | bulk_constants['response_mode']
28 |
29 | # Public: Issue a download request for all entities in an account.
30 | #
31 | # http://msdn.microsoft.com/en-us/library/jj885755.aspx
32 | #
33 | # Author:: alex.cavalli@offers.com
34 | #
35 | # === Parameters
36 | # +account_ids+ - Array of account ids
37 | # +entities+ - Array of entities
38 | # +options+ - Hash of additional options
39 | #
40 | # === Examples
41 | # service.download_campaigns_by_account_ids(
42 | # [1,2,3],
43 | # [:campaigns, :ad_groups, :keywords, :ads],
44 | # data_scope: :entity_data,
45 | # download_file_type: :csv,
46 | # format_version: 2.0,
47 | # last_sync_time_in_utc: "2001-10-26T21:32:52",
48 | # location_target_version: "Latest",
49 | # performance_stats_date_range: {
50 | # custom_date_range_end: {day: 31, month: 12, year: 2013},
51 | # custom_date_range_start: {day: 1, month: 12, year: 2013}
52 | # }
53 | # )
54 | # # => "1234567890"
55 | #
56 | # Returns:: String with the download request id
57 | #
58 | # Raises:: exception
59 | def download_campaigns_by_account_ids(account_ids, entities, options={})
60 | download_request = {
61 | account_ids: {"ins0:long" => account_ids},
62 | data_scope: data_scope_to_array(options[:data_scope]),
63 | download_file_type: DOWNLOAD_FILE_TYPES[options[:download_file_type].to_s],
64 | entities: entities_for_soap(entities),
65 | format_version: options[:format_version] || 4.0,
66 | last_sync_time_in_utc: options[:last_sync_time_in_utc],
67 | location_target_version: options[:location_target_version],
68 | performance_stats_date_range: options[:performance_stats_date_range]
69 | }
70 |
71 | response = call(:download_campaigns_by_account_ids,
72 | download_request)
73 | response_hash = get_response_hash(response, __method__)
74 | download_request_id = response_hash[:download_request_id]
75 | return download_request_id
76 | end
77 |
78 |
79 | # Public: Get an upload URL and corresponding request ID. The URL can be
80 | # used to post a bulk upload file to, and the request ID can be used to
81 | # track the status of the request once the file has been posted. See
82 | # http://msdn.microsoft.com/en-US/library/bing-ads-bulk-download-and-upload-guide.aspx#bulkupload
83 | #
84 | # Author:: alex.cavalli@offers.com
85 | #
86 | # === Parameters
87 | # +account_id+ - An account id to apply the bulk upload to
88 | # +options+ - Hash of additional options
89 | #
90 | # === Examples
91 | # service.get_bulk_upload_url(111)
92 | # # => {request_id: 222, upload_url: "http://www.example.com/"}
93 | #
94 | # Returns:: Hash with the upload URL and upload request ID
95 | #
96 | # Raises:: exception
97 | def get_bulk_upload_url(account_id, options={})
98 | response = call(:get_bulk_upload_url,
99 | response_mode: RESPONSE_MODES[options[:response_mode].to_s],
100 | account_id: account_id,
101 | )
102 | response_hash = get_response_hash(response, __method__)
103 | return response_hash
104 | end
105 |
106 |
107 | # Public: Post a bulk upload file to the Bing Ads API and return the bulk
108 | # request ID. This method does not map to a specific service call in the
109 | # Bing Ads API, but wraps the GetBulkUploadUrl call and POSTs the file
110 | # upload to the provided upload URL.
111 | # http://msdn.microsoft.com/en-US/library/bing-ads-bulk-download-and-upload-guide.aspx#bulkupload
112 | #
113 | # Author:: alex.cavalli@offers.com
114 | #
115 | # === Parameters
116 | # +file+ - A path to the CSV/TSV bulk file to upload
117 | # +account_id+ - An account id to apply the bulk upload to
118 | # +options+ - Hash of additional options
119 | #
120 | # === Examples
121 | # service.submit_bulk_upload_file("bulk_upload.csv", 111)
122 | # # => "1234567890"
123 | #
124 | # Returns:: String with the upload request id
125 | #
126 | # Raises:: exception
127 | def submit_bulk_upload_file(file, account_id, options={})
128 | upload_request = get_bulk_upload_url(account_id, options)
129 |
130 | # TODO: Extract this HTTP interface to separate object
131 | response = RestClient.post(
132 | upload_request[:upload_url],
133 | {file: File.new(file)},
134 | "UserName" => client_proxy.username,
135 | "Password" => client_proxy.password,
136 | "DeveloperToken" => client_proxy.developer_token,
137 | "CustomerId" => client_proxy.customer_id,
138 | "AccountId" => account_id,
139 | )
140 |
141 | raise "File upload failed. HTTP response:\n#{response.to_str}" unless response.code == 200
142 |
143 | return upload_request[:request_id]
144 | end
145 |
146 |
147 | # Public: Get the status of a report request
148 | #
149 | # Author:: alex.cavalli@offers.com
150 | #
151 | # === Parameters
152 | # +download_request_id+ - Identifier of the report request
153 | #
154 | # === Examples
155 | # service.get_bulk_download_status("12345")
156 | # # => BulkDownloadStatus
157 | #
158 | # Returns:: BulkDownloadStatus
159 | #
160 | # Raises:: exception
161 | def get_bulk_download_status(download_request_id)
162 | response = call(:get_bulk_download_status,
163 | {request_id: download_request_id} )
164 | response_hash = get_response_hash(response, __method__)
165 | return BingAdsApi::BulkDownloadStatus.new(response_hash)
166 | end
167 |
168 |
169 | # Public: Get the status of a upload request
170 | #
171 | # Author:: alex.cavalli@offers.com
172 | #
173 | # === Parameters
174 | # +upload_request_id+ - Identifier of the upload request
175 | #
176 | # === Examples
177 | # service.get_bulk_upload_status("12345")
178 | # # => BulkUploadStatus
179 | #
180 | # Returns:: BulkUploadStatus
181 | #
182 | # Raises:: exception
183 | def get_bulk_upload_status(upload_request_id)
184 | response = call(:get_bulk_upload_status,
185 | {request_id: upload_request_id} )
186 | response_hash = get_response_hash(response, __method__)
187 | return BingAdsApi::BulkUploadStatus.new(response_hash.merge('request_id' => upload_request_id))
188 | end
189 |
190 |
191 | private
192 | def get_service_name
193 | "bulk"
194 | end
195 |
196 |
197 | # Internal: Converts data scope symbols (in snake case) to camelcased
198 | # variants for SOAP.
199 | #
200 | # Author:: alex.cavalli@offers.com
201 | #
202 | # Returns:: Array
203 | def data_scope_to_array(data_scope)
204 | return nil unless data_scope
205 |
206 | valid_entities = BingAdsApi::Config.instance.bulk_constants['data_scope']
207 |
208 | Array(data_scope).map do |data_scope|
209 | if data_scope.is_a?(String)
210 | data_scope
211 | elsif data_scope.is_a?(Symbol)
212 | valid_entities[data_scope.to_s]
213 | end
214 | end
215 | end
216 |
217 |
218 | # Internal: Converts entity symbols (in snake case) to camelcased variants
219 | # for SOAP and joins them by whitespace characters.
220 | #
221 | # Author:: alex.cavalli@offers.com
222 | #
223 | # Returns:: String
224 | def entities_for_soap(entities)
225 | raise Exception.new("Invalid entities value: nil") unless entities
226 |
227 | valid_entities = BingAdsApi::Config.instance.bulk_constants['entities']
228 |
229 | entities.map do |entity|
230 | if entity.is_a?(String)
231 | entity
232 | elsif entity.is_a?(Symbol)
233 | valid_entities[entity.to_s]
234 | end
235 | end.join(" ")
236 | end
237 |
238 | end
239 | end
240 |
--------------------------------------------------------------------------------
/spec/campaign_management_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding : utf-8 -*-
2 | require 'spec_helper'
3 |
4 | # Author:: jlopezn@neonline.cl
5 | describe BingAdsApi::CampaignManagement do
6 |
7 | let(:default_options) do
8 | {
9 | environment: :sandbox,
10 | username: "ruby_bing_ads_sbx",
11 | password: "sandbox123",
12 | developer_token: "BBD37VB98",
13 | customer_id: "21025739",
14 | account_id: "8506945"
15 | }
16 | end
17 | let(:service) { BingAdsApi::CampaignManagement.new(default_options) }
18 |
19 | it "should initialize with options" do
20 | new_service = BingAdsApi::CampaignManagement.new(default_options)
21 | expect(new_service).not_to be_nil
22 | end
23 |
24 | describe "targets operations" do
25 |
26 | it "should add city target" do
27 | target = BingAdsApi::Target.new(
28 | location: BingAdsApi::LocationTarget.new(
29 | city_target: BingAdsApi::CityTarget.new(
30 | bids: [
31 | city_target_bid: BingAdsApi::CityTargetBid.new(
32 | bid_adjustment: 0,
33 | city: 'Sydney, NS AU',
34 | is_excluded: false
35 | )]
36 | )
37 | )
38 | )
39 | response = service.add_targets_to_library(default_options[:account_id], target)
40 | expect(response[:target_ids][:long]).not_to be_nil
41 | end
42 |
43 | it "should add radius target" do
44 | target = BingAdsApi::Target.new(
45 | location: BingAdsApi::LocationTarget.new(
46 | radius_target: BingAdsApi::RadiusTarget.new(
47 | bids: [
48 | radius_target_bid: BingAdsApi::RadiusTargetBid.new(
49 | bid_adjustment: 0,
50 | id: nil,
51 | is_excluded: false,
52 | latitude_degrees: 35.575156,
53 | longitude_degrees: -77.398931,
54 | radius: 20,
55 | radius_unit: BingAdsApi::DistanceUnits::MILES
56 | )]
57 | )
58 | )
59 | )
60 | response = service.add_targets_to_library(default_options[:account_id], target)
61 | expect(response[:target_ids][:long]).not_to be_nil
62 | end
63 |
64 | end
65 |
66 | describe "campaign operations" do
67 |
68 | it "should add a campaign" do
69 | name = "Test Campaign #{SecureRandom.uuid}"
70 | campaigns = [
71 | BingAdsApi::Campaign.new(
72 | budget_type: BingAdsApi::Campaign::DAILY_BUDGET_STANDARD,
73 | daily_budget: 2000,
74 | daylight_saving: "false",
75 | description: name + " description",
76 | name: name + " name",
77 | time_zone: BingAdsApi::Campaign::SANTIAGO
78 | )
79 | ]
80 | response = service.add_campaigns(default_options[:account_id], campaigns)
81 |
82 | expect(response[:campaign_ids][:long]).not_to be_nil
83 | end
84 |
85 | it "should get campaigns by account when there are no campaigns" do
86 | # clean all campaigns first
87 | campaign_ids = service.get_campaigns_by_account_id(default_options[:account_id]).map(&:id)
88 | campaign_ids.each_slice(100) do |ids|
89 | service.delete_campaigns(default_options[:account_id], ids)
90 | end
91 |
92 | response = service.get_campaigns_by_account_id(default_options[:account_id])
93 | expect(response).to be_kind_of(Array)
94 | expect(response).to be_empty
95 | end
96 |
97 | context "when a campaign has already been created" do
98 |
99 | before :each do
100 | @campaign_id = BingAdsFactory.create_campaign
101 | end
102 |
103 | it "should get campaigns by account when there's only one campaign" do
104 | response = service.get_campaigns_by_account_id(default_options[:account_id])
105 | expect(response).not_to be_nil
106 | expect(response).to be_kind_of(Array)
107 | end
108 |
109 | it "should get campaigns by account when there are multiple campaigns" do
110 | BingAdsFactory.create_campaign
111 | response = service.get_campaigns_by_account_id(default_options[:account_id])
112 | expect(response).not_to be_nil
113 | expect(response).to be_kind_of(Array)
114 | end
115 |
116 | it "should update campaigns" do
117 | name = "Test Campaign Update #{SecureRandom.uuid}"
118 | campaigns = [
119 | BingAdsApi::Campaign.new(
120 | id: @campaign_id,
121 | budget_type: BingAdsApi::Campaign::DAILY_BUDGET_STANDARD,
122 | daily_budget: 2000,
123 | daylight_saving: "false",
124 | description: name + " description",
125 | name: name + " name",
126 | time_zone: BingAdsApi::Campaign::SANTIAGO
127 | )
128 | ]
129 |
130 | response = service.update_campaigns(default_options[:account_id], campaigns)
131 |
132 | expect(response).not_to be_nil
133 | end
134 |
135 | it "should delete campaigns" do
136 | campaign_ids = [@campaign_id]
137 |
138 | response = service.delete_campaigns(default_options[:account_id], campaign_ids)
139 |
140 | expect(response).not_to be_nil
141 | end
142 |
143 | end
144 | end
145 |
146 | describe "ad group operations" do
147 |
148 | before :each do
149 | @campaign_id = BingAdsFactory.create_campaign
150 | end
151 |
152 | it "should add an ad group" do
153 | name = "Ad Group #{SecureRandom.uuid}"
154 | ad_groups = [
155 | BingAdsApi::AdGroup.new(
156 | ad_distribution: BingAdsApi::AdGroup::SEARCH,
157 | language: BingAdsApi::AdGroup::SPANISH,
158 | name: name + " name",
159 | pricing_model: BingAdsApi::AdGroup::CPC,
160 | bidding_model: BingAdsApi::AdGroup::KEYWORD
161 | )
162 | ]
163 | response = service.add_ad_groups(@campaign_id, ad_groups)
164 |
165 | expect(response[:ad_group_ids][:long]).not_to be_nil
166 | end
167 |
168 | it "should get ad groups by campaign when there are no ad groups" do
169 | response = service.get_ad_groups_by_campaign_id(@campaign_id)
170 | expect(response).to be_kind_of(Array)
171 | expect(response).to be_empty
172 | end
173 |
174 | context "when an ad group has already been created" do
175 |
176 | before :each do
177 | @ad_group_id = BingAdsFactory.create_ad_group(@campaign_id)
178 | end
179 |
180 | it "should get ad groups by campaign when there's only one ad group" do
181 | response = service.get_ad_groups_by_campaign_id(@campaign_id)
182 |
183 | expect(response).not_to be_empty
184 | expect(response).to be_kind_of(Array)
185 | end
186 |
187 | it "should get ad groups by campaign when there are multiple ad groups" do
188 | BingAdsFactory.create_ad_group(@campaign_id)
189 | response = service.get_ad_groups_by_campaign_id(@campaign_id)
190 |
191 | expect(response).not_to be_empty
192 | expect(response).to be_kind_of(Array)
193 | end
194 |
195 | it "should get ad groups by ids when there's only one ad group" do
196 | response = service.get_ad_groups_by_ids(@campaign_id, [@ad_group_id])
197 |
198 | expect(response).not_to be_nil
199 | expect(response.size).to eq(1)
200 | end
201 |
202 | it "should get ad groups by ids when there are multiple ad groups" do
203 | ad_group_id_2 = BingAdsFactory.create_ad_group(@campaign_id)
204 | response = service.get_ad_groups_by_ids(@campaign_id, [@ad_group_id, ad_group_id_2])
205 |
206 | expect(response).not_to be_nil
207 | expect(response.size).to eq(2)
208 | end
209 |
210 | it "should update ad groups" do
211 | name = "Test Ad Group Update #{SecureRandom.uuid}"
212 | ad_groups = [
213 | BingAdsApi::AdGroup.new(
214 | id: @ad_group_id,
215 | name: name
216 | )
217 | ]
218 |
219 | response = service.update_ad_groups(@campaign_id, ad_groups)
220 | end
221 |
222 | end
223 | end
224 |
225 | describe "ad operations" do
226 |
227 | before :each do
228 | campaign_id = BingAdsFactory.create_campaign
229 | @ad_group_id = BingAdsFactory.create_ad_group(campaign_id)
230 | end
231 |
232 | describe "expanded text ads" do
233 |
234 | it "should add a single ad" do
235 |
236 | text_ad = BingAdsApi::ExpandedTextAd.new(
237 | final_urls: {'ins1:string'=> 'http://test.com'},
238 | status: BingAdsApi::Ad::ACTIVE,
239 | text: 'Expanded Text Ad',
240 | title_part_1: 'Expanded Title',
241 | title_part_2: 'Expanded Title 2',
242 | path_1: 'test',
243 | path_2: 'test'
244 | )
245 |
246 | response = service.add_ads(@ad_group_id, text_ad)
247 | expect(response).not_to be_nil
248 | expect(response[:partial_errors]).to be_nil
249 | expect(response[:ad_ids][:long]).not_to be_nil
250 | end
251 | end
252 |
253 |
254 | describe "text ads" do
255 |
256 | it "should add a single ad" do
257 | text_ad = BingAdsApi::TextAd.new(
258 | status: BingAdsApi::Ad::ACTIVE,
259 | destination_url: "http://www.adxion.com",
260 | display_url: "AdXion.com",
261 | text: "Text Ad #{SecureRandom.uuid}",
262 | title: "Text Ad"
263 | )
264 |
265 | response = service.add_ads(@ad_group_id, text_ad)
266 | expect(response).not_to be_nil
267 | expect(response[:partial_errors]).to be_nil
268 | expect(response[:ad_ids][:long]).not_to be_nil
269 | end
270 |
271 | it "should add multiple ads" do
272 | text_ads = [
273 | BingAdsApi::TextAd.new(
274 | status: BingAdsApi::Ad::ACTIVE,
275 | destination_url: "http://www.adxion.com",
276 | display_url: "AdXion.com",
277 | text: "TextAd #{SecureRandom.uuid}",
278 | title: "TextAd"
279 | ),
280 | BingAdsApi::TextAd.new(
281 | status: BingAdsApi::Ad::ACTIVE,
282 | destination_url: "http://www.adxion.com",
283 | display_url: "AdXion.com",
284 | text: "TextAd 2 #{SecureRandom.uuid}",
285 | title: "TextAd 2"
286 | )
287 | ]
288 |
289 | response = service.add_ads(@ad_group_id, text_ads)
290 | expect(response).not_to be_nil
291 | expect(response[:partial_errors]).to be_nil
292 | expect(response[:ad_ids][:long]).not_to be_nil
293 | end
294 |
295 | end
296 |
297 | it "should add an ad with partial errors" do
298 | text_ad = BingAdsApi::TextAd.new(
299 | status: BingAdsApi::Ad::INACTIVE,
300 | destination_url: "http://www.adxion.com",
301 | display_url: "http://www.adxion.com",
302 | text: "Text Ad #{SecureRandom.uuid}",
303 | title: "Text Ad #{SecureRandom.uuid}"
304 | )
305 |
306 | response = service.add_ads(@ad_group_id, text_ad)
307 | expect(response).not_to be_nil
308 | expect(response[:partial_errors]).not_to be_nil
309 | end
310 |
311 | it "should get ads by ad group id when there are no ads" do
312 | ads = service.get_ads_by_ad_group_id(@ad_group_id)
313 | expect(ads).to be_empty
314 | end
315 |
316 | context "when an ad has already been created" do
317 |
318 | before :each do
319 | @ad_id = BingAdsFactory.create_text_ad(@ad_group_id)
320 | end
321 |
322 | it "should get ads by ad group id when there's only one ad" do
323 | ads = service.get_ads_by_ad_group_id(@ad_group_id)
324 | expect(ads).not_to be_empty
325 | ad = ads.first
326 | expect(ad).to be_kind_of(BingAdsApi::Ad)
327 | end
328 |
329 | it "should get ads by ad group id when there are multiple ads" do
330 | BingAdsFactory.create_text_ad(@ad_group_id)
331 | ads = service.get_ads_by_ad_group_id(@ad_group_id)
332 | expect(ads).not_to be_empty
333 | ads.each do |ad|
334 | expect(ad).to be_kind_of(BingAdsApi::Ad)
335 | end
336 | end
337 |
338 | it "should get ads by ids when there's only one ad" do
339 | ads_by_ids = service.get_ads_by_ids(@ad_group_id, [@ad_id])
340 | expect(ads_by_ids).not_to be_nil
341 | expect(ads_by_ids.size).to eq(1)
342 | end
343 |
344 | it "should get ads by ids when there are multiple ads" do
345 | ad_id_2 = BingAdsFactory.create_text_ad(@ad_group_id)
346 | ads_by_ids = service.get_ads_by_ids(@ad_group_id, [@ad_id, ad_id_2])
347 | expect(ads_by_ids).not_to be_nil
348 | expect(ads_by_ids.size).to eq(2)
349 | ads_by_ids.each do |ad|
350 | expect(ad).to be_kind_of(BingAdsApi::Ad)
351 | end
352 | end
353 |
354 | it "should update ads" do
355 | text_ad = BingAdsApi::TextAd.new(
356 | id: @ad_id,
357 | status: BingAdsApi::Ad::ACTIVE,
358 | destination_url: "http://www.adxion.com",
359 | display_url: "http://www.adxion.com",
360 | text: "Ad #{SecureRandom.uuid}",
361 | title: "Title"
362 | )
363 |
364 | response = service.update_ads(@ad_group_id, [text_ad])
365 | expect(response).not_to be_nil
366 | expect(response[:partial_errors]).to be_nil
367 | end
368 |
369 | end
370 | end
371 |
372 | describe "keyword operations" do
373 |
374 | before :each do
375 | campaign_id = BingAdsFactory.create_campaign
376 | @ad_group_id = BingAdsFactory.create_ad_group(campaign_id)
377 | end
378 |
379 | it "should add a single keyword" do
380 | keyword = BingAdsApi::Keyword.new(
381 | bid: BingAdsApi::Bid.new(amount: 1.23),
382 | destination_url: "http://www.adxion.com",
383 | match_type: BingAdsApi::Keyword::EXACT,
384 | status: BingAdsApi::Keyword::ACTIVE,
385 | text: "Keyword #{SecureRandom.uuid}"
386 | )
387 |
388 | response = service.add_keywords(@ad_group_id, keyword)
389 | expect(response).not_to be_nil
390 | expect(response[:partial_errors]).to be_nil
391 | expect(response[:keyword_ids][:long]).not_to be_nil
392 | end
393 |
394 | it "should add multiple keywords" do
395 | keywords = [
396 | BingAdsApi::Keyword.new(
397 | bid: BingAdsApi::Bid.new(amount: 1.23),
398 | destination_url: "http://www.adxion.com",
399 | match_type: BingAdsApi::Keyword::EXACT,
400 | status: BingAdsApi::Keyword::ACTIVE,
401 | text: "Keyword #{SecureRandom.uuid}"
402 | ),
403 | BingAdsApi::Keyword.new(
404 | bid: BingAdsApi::Bid.new(amount: 1.23),
405 | destination_url: "http://www.adxion.com",
406 | match_type: BingAdsApi::Keyword::EXACT,
407 | status: BingAdsApi::Keyword::ACTIVE,
408 | text: "Keyword #{SecureRandom.uuid}"
409 | )
410 | ]
411 |
412 | response = service.add_keywords(@ad_group_id, keywords)
413 | expect(response).not_to be_nil
414 | expect(response[:partial_errors]).to be_nil
415 | expect(response[:keyword_ids][:long]).not_to be_nil
416 | end
417 |
418 | it "should add a keyword with partial errors" do
419 | keyword = BingAdsApi::Keyword.new(
420 | # missing bid
421 | destination_url: "http://www.bing.com/",
422 | match_type: BingAdsApi::Keyword::EXACT,
423 | status: BingAdsApi::Keyword::ACTIVE,
424 | text: "Keyword #{SecureRandom.uuid}"
425 | )
426 |
427 | response = service.add_keywords(@ad_group_id, keyword)
428 | expect(response).not_to be_nil
429 | expect(response[:partial_errors]).not_to be_nil
430 | end
431 |
432 | it "should get keywords by ad group id when there are no keywords" do
433 | keywords = service.get_keywords_by_ad_group_id(@ad_group_id)
434 | expect(keywords).to be_empty
435 | end
436 |
437 | context "when a keyword has already been created" do
438 |
439 | before :each do
440 | @keyword_id = BingAdsFactory.create_keyword(@ad_group_id)
441 | end
442 |
443 | it "should get keywords by ad group id when there's only one keyword" do
444 | keywords = service.get_keywords_by_ad_group_id(@ad_group_id)
445 | expect(keywords).not_to be_empty
446 | keyword = keywords.first
447 | expect(keyword).to be_kind_of(BingAdsApi::Keyword)
448 | end
449 |
450 | it "should get keywords by ad group id when there are multiple keywords" do
451 | BingAdsFactory.create_keyword(@ad_group_id)
452 | keywords = service.get_keywords_by_ad_group_id(@ad_group_id)
453 | expect(keywords).not_to be_empty
454 | keywords.each do |keyword|
455 | expect(keyword).to be_kind_of(BingAdsApi::Keyword)
456 | end
457 | end
458 |
459 | it "should get keywords by ids when there's only one keyword" do
460 | keywords_by_ids = service.get_keywords_by_ids(@ad_group_id, [@keyword_id])
461 | expect(keywords_by_ids).not_to be_nil
462 | expect(keywords_by_ids.size).to eq(1)
463 | end
464 |
465 | it "should get keywords by ids when there are multiple keywords" do
466 | keyword_id_2 = BingAdsFactory.create_keyword(@ad_group_id)
467 | keywords_by_ids = service.get_keywords_by_ids(@ad_group_id, [@keyword_id, keyword_id_2])
468 | expect(keywords_by_ids).not_to be_nil
469 | expect(keywords_by_ids.size).to eq(2)
470 | keywords_by_ids.each do |keyword|
471 | expect(keyword).to be_kind_of(BingAdsApi::Keyword)
472 | end
473 | end
474 |
475 | it "should update keywords" do
476 | keyword = BingAdsApi::Keyword.new(
477 | id: @keyword_id,
478 | bid: BingAdsApi::Bid.new(amount: 99.99)
479 | )
480 |
481 | response = service.update_keywords(@ad_group_id, [keyword])
482 | expect(response).not_to be_nil
483 | expect(response[:partial_errors]).to be_nil
484 | end
485 |
486 | end
487 | end
488 | end
489 |
--------------------------------------------------------------------------------