soapy_bing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env +6 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/README.md +63 -0
- data/Rakefile +7 -0
- data/circle.yml +6 -0
- data/lib/soapy_bing/account.rb +12 -0
- data/lib/soapy_bing/ads/reports/base.rb +83 -0
- data/lib/soapy_bing/ads/reports/campaign_performance_report.rb +23 -0
- data/lib/soapy_bing/ads/reports/parsers/csv_parser.rb +45 -0
- data/lib/soapy_bing/ads/reports/parsers.rb +1 -0
- data/lib/soapy_bing/ads/reports.rb +3 -0
- data/lib/soapy_bing/ads.rb +22 -0
- data/lib/soapy_bing/helpers/class_name.rb +9 -0
- data/lib/soapy_bing/helpers.rb +1 -0
- data/lib/soapy_bing/oauth_credentials.rb +43 -0
- data/lib/soapy_bing/param_guard.rb +26 -0
- data/lib/soapy_bing/soap/request/base.rb +37 -0
- data/lib/soapy_bing/soap/request/poll_generate_report_request.rb +33 -0
- data/lib/soapy_bing/soap/request/submit_generate_report_request.rb +16 -0
- data/lib/soapy_bing/soap/request.rb +3 -0
- data/lib/soapy_bing/soap/response/base.rb +16 -0
- data/lib/soapy_bing/soap/response/payload.rb +15 -0
- data/lib/soapy_bing/soap/response/poll_generate_report_response.rb +13 -0
- data/lib/soapy_bing/soap/response/report_status.rb +33 -0
- data/lib/soapy_bing/soap/response/submit_generate_report_response.rb +11 -0
- data/lib/soapy_bing/soap/response.rb +5 -0
- data/lib/soapy_bing/soap/template_renderer.rb +21 -0
- data/lib/soapy_bing/soap/templates/poll_generate_report.erb.xml +18 -0
- data/lib/soapy_bing/soap/templates/submit_generate_report.erb.xml +46 -0
- data/lib/soapy_bing/soap.rb +3 -0
- data/lib/soapy_bing/version.rb +3 -0
- data/lib/soapy_bing.rb +11 -0
- data/lib/tasks/console.rake +5 -0
- data/lib/tasks/coverage.rake +6 -0
- data/lib/tasks/spec.rake +4 -0
- data/soapy_bing.gemspec +34 -0
- data/spec/fixtures/reports/campaign_performance_report.csv +37 -0
- data/spec/fixtures/reports/campaign_performance_report.json +146 -0
- data/spec/fixtures/soap_templates/simple.erb.xml +2 -0
- data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_pending_status.yml +168 -0
- data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_status.yml +284 -0
- data/spec/fixtures/vcr_cassettes/oauth_credentials/access_token/with_successful_status.yml +42 -0
- data/spec/integration/soapy_bing/ads/reports/campaign_performance_report_spec.rb +39 -0
- data/spec/integration/soapy_bing/oauth_credentials_spec.rb +10 -0
- data/spec/simplecov_setup.rb +9 -0
- data/spec/soapy_bing/account_spec.rb +80 -0
- data/spec/soapy_bing/ads/reports/campaign_performance_report_spec.rb +41 -0
- data/spec/soapy_bing/ads/reports/parsers/csv_parser_spec.rb +31 -0
- data/spec/soapy_bing/ads_spec.rb +32 -0
- data/spec/soapy_bing/helpers/class_name_spec.rb +14 -0
- data/spec/soapy_bing/oauth_credentials_spec.rb +108 -0
- data/spec/soapy_bing/param_guard_spec.rb +43 -0
- data/spec/soapy_bing/soap/request/base_spec.rb +54 -0
- data/spec/soapy_bing/soap/request/poll_generate_report_request_spec.rb +59 -0
- data/spec/soapy_bing/soap/response/base_spec.rb +12 -0
- data/spec/soapy_bing/soap/response/payload_spec.rb +25 -0
- data/spec/soapy_bing/soap/response/poll_generate_report_response_spec.rb +25 -0
- data/spec/soapy_bing/soap/response/report_status_spec.rb +91 -0
- data/spec/soapy_bing/soap/response/submit_generate_report_response_spec.rb +19 -0
- data/spec/soapy_bing/soap/template_renderer_spec.rb +24 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/dotenv.rb +3 -0
- data/spec/support/vcr.rb +101 -0
- metadata +305 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 07b8f667808f19487bb63afecce80900cbdb0116
|
4
|
+
data.tar.gz: cef996017af61aa78230fba06560e3c89304c9a8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff3885794184b39eae3827dba80d1b512342b80fa9ef34965c9e8c9477808fcc9196ab2a35262c150e3e4444cceba8d2c6fc3db268bfd1fd70c98e9b5fc60c9a
|
7
|
+
data.tar.gz: 2a81559fa1fe6f2d9db5201489d87b3749cf52542cdbeeee689c760b21c67d10de72fd70ac5622b4fb9022866f2b299b041d49499d5df26985a6aba3121cf445
|
data/.env
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
export BING_ADS_OAUTH_CLIENT_ID='bing-ads-oauth-client-id'
|
2
|
+
export BING_ADS_OAUTH_CLIENT_SECRET='bing-ads-oauth-client-secret'
|
3
|
+
export BING_ADS_OAUTH_REFRESH_TOKEN='bing-ads-oauth-refresh-token'
|
4
|
+
export BING_ADS_DEVELOPER_TOKEN='bing-ads-developer-token'
|
5
|
+
export BING_ADS_ACCOUNT_ID='bing-ads-account-id'
|
6
|
+
export BING_ADS_CUSTOMER_ID='bing-ads-customer-id'
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# SoapyBing
|
2
|
+
[![Circle CI](https://circleci.com/gh/ad2games/soapy_bing.png?style=shield)](https://circleci.com/gh/ad2games/soapy_bing)
|
3
|
+
|
4
|
+
Client library for [Bing Ads API](https://msdn.microsoft.com/en-us/library/bing-ads-overview)
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
In your Gemfile
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'soapy-bing', git: 'https://github.com/ad2games/soapy-bing'
|
12
|
+
```
|
13
|
+
|
14
|
+
## Usage Examples
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
bing_ads = SoapyBing::Ads.new
|
18
|
+
report = bing_ads.campaign_performance_report(
|
19
|
+
date_start: '2015-10-14',
|
20
|
+
date_end: '2015-10-14',
|
21
|
+
|
22
|
+
settings: { # optional
|
23
|
+
columns: [''] # see SoapyBing::Ads::Report::Base::DEFAULT_REPORT_SETTINGS[:columns]
|
24
|
+
}
|
25
|
+
)
|
26
|
+
report.rows # =>
|
27
|
+
# [{
|
28
|
+
# "HourOfDay" => "0",
|
29
|
+
# "CampaignName" => "My Campaign",
|
30
|
+
# "Impressions" => "100",
|
31
|
+
# "Clicks" => "47",
|
32
|
+
# "Spend" => "12.65"
|
33
|
+
# }, {
|
34
|
+
# ...
|
35
|
+
# }]
|
36
|
+
```
|
37
|
+
|
38
|
+
## Authentication
|
39
|
+
|
40
|
+
Authentication attributes could be passed explicitly when new instance of SoapyBing::Ads is created.
|
41
|
+
Or they could be configured as Envronment variables.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
SoapyBing::Ads.new(
|
45
|
+
oauth: { # optional, could be configured via environment variables as
|
46
|
+
client_id: '', # ENV['BING_ADS_OAUTH_CLIENT_ID']
|
47
|
+
client_secret: '', # ENV['BING_ADS_OAUTH_CLIENT_SECRET']
|
48
|
+
refresh_token: '' # ENV['BING_ADS_OAUTH_REFRESH_TOKEN']
|
49
|
+
},
|
50
|
+
account: { # optional, could be configured via environment variables as
|
51
|
+
developer_token: '', # ENV['BING_ADS_DEVELOPER_TOKEN']
|
52
|
+
account_id: '', # ENV['BING_ADS_ACCOUNT_ID']
|
53
|
+
customer_id: '' # ENV['BING_ADS_CUSTOMER_ID']
|
54
|
+
}
|
55
|
+
)
|
56
|
+
```
|
57
|
+
|
58
|
+
## Links
|
59
|
+
* MS Live Applications [https://account.live.com/developers/applications](https://account.live.com/developers/applications)
|
60
|
+
* Bing Ads User Authentication with OAuth [https://msdn.microsoft.com/en-us/library/bing-ads-user-authentication-oauth-guide.aspx](https://msdn.microsoft.com/en-us/library/bing-ads-user-authentication-oauth-guide.aspx)
|
61
|
+
* Getting Started With the Bing Ads API [https://msdn.microsoft.com/en-us/library/bing-ads-getting-started.aspx](https://msdn.microsoft.com/en-us/library/bing-ads-getting-started.aspx)
|
62
|
+
* Bing Ads API Reference [https://msdn.microsoft.com/en-US/library/bing-ads-api-reference.aspx](https://msdn.microsoft.com/en-US/library/bing-ads-api-reference.aspx)
|
63
|
+
* Reporting Service Reference [https://msdn.microsoft.com/en-US/library/bing-ads-reporting-service-reference](https://msdn.microsoft.com/en-US/library/bing-ads-reporting-service-reference)
|
data/Rakefile
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module SoapyBing
|
2
|
+
class Account
|
3
|
+
attr_reader :developer_token, :account_id, :customer_id
|
4
|
+
|
5
|
+
def initialize(account_options)
|
6
|
+
param_guard = ParamGuard.new(account_options, env_namespace: 'BING_ADS')
|
7
|
+
@developer_token = param_guard.require!(:developer_token)
|
8
|
+
@account_id = param_guard.require!(:account_id)
|
9
|
+
@customer_id = param_guard.require!(:customer_id)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'zip'
|
4
|
+
|
5
|
+
module SoapyBing
|
6
|
+
class Ads
|
7
|
+
module Reports
|
8
|
+
class Base
|
9
|
+
class UnknownParserError < StandardError; end
|
10
|
+
include Helpers::ClassName
|
11
|
+
|
12
|
+
DEFAULT_REPORT_SETTINGS = {
|
13
|
+
format: 'Csv',
|
14
|
+
language: 'English',
|
15
|
+
name: 'MyReport',
|
16
|
+
aggregation: 'HourOfDay',
|
17
|
+
columns: %w(TimePeriod CampaignName Impressions Clicks Spend)
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
attr_reader :oauth_credentials, :account, :settings
|
21
|
+
|
22
|
+
def initialize(options)
|
23
|
+
@oauth_credentials = options.fetch(:oauth_credentials)
|
24
|
+
@account = options.fetch(:account)
|
25
|
+
@settings = OpenStruct.new(DEFAULT_REPORT_SETTINGS.merge(options.fetch(:settings, {})))
|
26
|
+
end
|
27
|
+
|
28
|
+
def rows
|
29
|
+
@rows ||= parser_class.new(report_body).rows
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parser_class
|
35
|
+
class_name = "#{settings.format.upcase}Parser".to_sym
|
36
|
+
fail UnknownParserError, class_name unless Parsers.constants.include?(class_name)
|
37
|
+
Parsers.const_get class_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def download_url
|
41
|
+
@url ||=
|
42
|
+
Soap::Request::PollGenerateReportRequest
|
43
|
+
.new(context: poll_generate_report_context)
|
44
|
+
.perform
|
45
|
+
.payload
|
46
|
+
end
|
47
|
+
|
48
|
+
def poll_generate_report_context
|
49
|
+
{
|
50
|
+
account: account,
|
51
|
+
authentication_token: oauth_credentials.access_token,
|
52
|
+
request_id: request_id
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def report_body
|
57
|
+
@report_body ||=
|
58
|
+
Zip::InputStream.open(OpenURI.open_uri(download_url)) do |archive_io|
|
59
|
+
file_io = archive_io.get_next_entry.get_input_stream
|
60
|
+
file_io.read
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_id
|
65
|
+
@request_id ||=
|
66
|
+
Soap::Request::SubmitGenerateReportRequest
|
67
|
+
.new(context: submit_generate_report_context)
|
68
|
+
.perform
|
69
|
+
.payload
|
70
|
+
end
|
71
|
+
|
72
|
+
def submit_generate_report_context
|
73
|
+
{
|
74
|
+
account: account,
|
75
|
+
authentication_token: oauth_credentials.access_token,
|
76
|
+
settings: settings,
|
77
|
+
report_class: class_name
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module SoapyBing
|
4
|
+
class Ads
|
5
|
+
module Reports
|
6
|
+
class CampaignPerformanceReport < Base
|
7
|
+
attr_reader :date_range
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
super(options)
|
11
|
+
@date_range = Range.new(
|
12
|
+
Date.parse(options.fetch(:date_start)),
|
13
|
+
Date.parse(options.fetch(:date_end))
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def submit_generate_report_context
|
18
|
+
super.merge(date_range: date_range)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module SoapyBing
|
4
|
+
class Ads
|
5
|
+
module Reports
|
6
|
+
module Parsers
|
7
|
+
class CSVParser
|
8
|
+
class FormatError < StandardError; end
|
9
|
+
|
10
|
+
CSV_PAYLOAD_OFFSET_FRONT = 10 # First 10 csv lines are report metadata
|
11
|
+
CSV_PAYLOAD_OFFSET_BACK = 2 # Last 2 csv lines are Microsoft copyright
|
12
|
+
|
13
|
+
def initialize(raw)
|
14
|
+
@raw = raw
|
15
|
+
end
|
16
|
+
|
17
|
+
def rows
|
18
|
+
@rows ||= begin
|
19
|
+
header, *body = extract_csv_payload
|
20
|
+
fail FormatError if body.size != payload_rows_number
|
21
|
+
body.map { |row| header.zip(row).to_h }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :raw
|
28
|
+
|
29
|
+
def extract_csv_payload
|
30
|
+
text = raw.dup
|
31
|
+
text.force_encoding(Encoding::UTF_8).encode! unless text.encoding == Encoding::UTF_8
|
32
|
+
text.sub!(/^\xEF\xBB\xBF/, '') # cleanup BOM
|
33
|
+
|
34
|
+
csv_rows = CSV.parse(text)
|
35
|
+
csv_rows[CSV_PAYLOAD_OFFSET_FRONT...-CSV_PAYLOAD_OFFSET_BACK]
|
36
|
+
end
|
37
|
+
|
38
|
+
def payload_rows_number
|
39
|
+
raw.match(/"Rows: (\d+)"/)[1].to_i
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'soapy_bing/ads/reports/parsers/csv_parser'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'soapy_bing/ads/reports'
|
2
|
+
|
3
|
+
module SoapyBing
|
4
|
+
class Ads
|
5
|
+
attr_reader :oauth_credentials, :account
|
6
|
+
|
7
|
+
def initialize(oauth: {}, account: {})
|
8
|
+
@oauth_credentials = OauthCredentials.new(oauth)
|
9
|
+
@account = Account.new(account)
|
10
|
+
end
|
11
|
+
|
12
|
+
def campaign_performance_report(date_start:, date_end:, settings: {})
|
13
|
+
Reports::CampaignPerformanceReport.new(
|
14
|
+
oauth_credentials: oauth_credentials,
|
15
|
+
account: account,
|
16
|
+
date_start: date_start,
|
17
|
+
date_end: date_end,
|
18
|
+
settings: settings
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'soapy_bing/helpers/class_name'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module SoapyBing
|
4
|
+
class OauthCredentials
|
5
|
+
class TokenRefreshError < StandardError; end
|
6
|
+
|
7
|
+
TOKEN_URL = 'https://login.live.com/oauth20_token.srf'.freeze
|
8
|
+
|
9
|
+
attr_reader :client_id, :client_secret, :refresh_token, :token_url
|
10
|
+
|
11
|
+
def initialize(oauth_options = {})
|
12
|
+
param_guard = ParamGuard.new(oauth_options, env_namespace: 'BING_ADS_OAUTH')
|
13
|
+
@client_id = param_guard.require!(:client_id)
|
14
|
+
@client_secret = param_guard.require!(:client_secret)
|
15
|
+
@refresh_token = param_guard.require!(:refresh_token)
|
16
|
+
@token_url = oauth_options.fetch(:token_url, ENV['BING_ADS_OAUTH_TOKEN_URL'] || TOKEN_URL)
|
17
|
+
end
|
18
|
+
|
19
|
+
def access_token
|
20
|
+
@access_token ||= request_access_token
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def request_access_token
|
26
|
+
resp = HTTParty.post(token_url, body: access_token_params)
|
27
|
+
if resp.code == 200
|
28
|
+
resp['access_token']
|
29
|
+
else
|
30
|
+
fail TokenRefreshError
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def access_token_params
|
35
|
+
{
|
36
|
+
client_id: client_id,
|
37
|
+
client_secret: client_secret,
|
38
|
+
grant_type: 'refresh_token',
|
39
|
+
refresh_token: refresh_token
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SoapyBing
|
2
|
+
class ParamGuard
|
3
|
+
class ParamRequiredError < StandardError; end
|
4
|
+
|
5
|
+
def initialize(local_options, env_namespace: '')
|
6
|
+
@local_options = local_options
|
7
|
+
@env_namespace = env_namespace
|
8
|
+
end
|
9
|
+
|
10
|
+
def require!(name)
|
11
|
+
local_options.fetch(name, ENV[env_var_name(name)]) || fail(ParamRequiredError, err_msg(name))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :local_options, :env_namespace
|
17
|
+
|
18
|
+
def err_msg(name)
|
19
|
+
"#{name} have to be passed explicitly or via ENV['#{env_var_name(name)}']"
|
20
|
+
end
|
21
|
+
|
22
|
+
def env_var_name(name)
|
23
|
+
(env_namespace.empty? ? name : "#{env_namespace}_#{name}").upcase.tr('-', '_')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module SoapyBing
|
4
|
+
module Soap
|
5
|
+
module Request
|
6
|
+
class Base
|
7
|
+
include Helpers::ClassName
|
8
|
+
|
9
|
+
DEFAULT_HTTP_HEADERS = {
|
10
|
+
'Content-Type' => 'text/xml;charset=UTF-8'
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_reader :context
|
14
|
+
|
15
|
+
def initialize(context:)
|
16
|
+
@context = context
|
17
|
+
end
|
18
|
+
|
19
|
+
def post(url, body: default_body, headers: default_headers)
|
20
|
+
HTTParty.post(url, body: body, headers: headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_body
|
24
|
+
TemplateRenderer.new(context).render(action_name.underscore)
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_headers
|
28
|
+
DEFAULT_HTTP_HEADERS.merge('SOAPAction' => action_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def action_name
|
32
|
+
class_name.sub(/Request$/, '')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SoapyBing
|
2
|
+
module Soap
|
3
|
+
module Request
|
4
|
+
class PollGenerateReportRequest < Base
|
5
|
+
class FailedStatusError < StandardError; end
|
6
|
+
class PendingStatusError < StandardError; end
|
7
|
+
class PollingTimeoutError < StandardError; end
|
8
|
+
|
9
|
+
API_BASE_URL = 'https://reporting.api.bingads.microsoft.com'.freeze
|
10
|
+
API_VERSION = 9
|
11
|
+
API_ENDPOINT =
|
12
|
+
"#{API_BASE_URL}/Api/Advertiser/Reporting/V#{API_VERSION}/ReportingService.svc".freeze
|
13
|
+
|
14
|
+
POLLING_TRIES = 40
|
15
|
+
|
16
|
+
def perform
|
17
|
+
Retryable.retryable(tries: POLLING_TRIES, on: PendingStatusError) { poll! }
|
18
|
+
rescue PendingStatusError
|
19
|
+
raise PollingTimeoutError
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def poll!
|
25
|
+
response = Response::PollGenerateReportResponse.new(post(API_ENDPOINT))
|
26
|
+
fail PendingStatusError if response.pending?
|
27
|
+
fail FailedStatusError if response.error?
|
28
|
+
response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SoapyBing
|
2
|
+
module Soap
|
3
|
+
module Request
|
4
|
+
class SubmitGenerateReportRequest < Base
|
5
|
+
API_BASE_URL = 'https://reporting.api.bingads.microsoft.com'.freeze
|
6
|
+
API_VERSION = 9
|
7
|
+
API_ENDPOINT =
|
8
|
+
"#{API_BASE_URL}/Api/Advertiser/Reporting/V#{API_VERSION}/ReportingService.svc".freeze
|
9
|
+
|
10
|
+
def perform
|
11
|
+
Response::SubmitGenerateReportResponse.new(post(API_ENDPOINT))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SoapyBing
|
2
|
+
module Soap
|
3
|
+
module Response
|
4
|
+
module ReportStatus
|
5
|
+
include Helpers::ClassName
|
6
|
+
|
7
|
+
def status
|
8
|
+
@status ||= extract_status
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_status
|
12
|
+
report_status['Status']
|
13
|
+
end
|
14
|
+
|
15
|
+
def report_status
|
16
|
+
body['Envelope']['Body'][class_name]['ReportRequestStatus']
|
17
|
+
end
|
18
|
+
|
19
|
+
def error?
|
20
|
+
status == 'Error'
|
21
|
+
end
|
22
|
+
|
23
|
+
def success?
|
24
|
+
status == 'Success'
|
25
|
+
end
|
26
|
+
|
27
|
+
def pending?
|
28
|
+
status == 'Pending'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'erubis'
|
3
|
+
|
4
|
+
module SoapyBing
|
5
|
+
module Soap
|
6
|
+
class TemplateRenderer < OpenStruct
|
7
|
+
TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
def render(template_name)
|
10
|
+
template_body = read(template_name)
|
11
|
+
Erubis::XmlEruby.new(template_body).result(binding)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def read(name)
|
17
|
+
File.read(File.join(TEMPLATE_PATH, "#{name}.erb.xml"))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
4
|
+
xmlns:tns="https://bingads.microsoft.com/Reporting/v9"
|
5
|
+
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
|
6
|
+
xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
|
7
|
+
<env:Header>
|
8
|
+
<tns:CustomerAccountId><%= account.account_id %></tns:CustomerAccountId>
|
9
|
+
<tns:CustomerId><%= account.customer_id %></tns:CustomerId>
|
10
|
+
<tns:DeveloperToken><%= account.developer_token %></tns:DeveloperToken>
|
11
|
+
<tns:AuthenticationToken><%= authentication_token %></tns:AuthenticationToken>
|
12
|
+
</env:Header>
|
13
|
+
<env:Body>
|
14
|
+
<tns:PollGenerateReportRequest>
|
15
|
+
<tns:ReportRequestId xsi:nil="false"><%= request_id %></tns:ReportRequestId>
|
16
|
+
</tns:PollGenerateReportRequest>
|
17
|
+
</env:Body>
|
18
|
+
</env:Envelope>
|