soapy_bing 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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>
|