soapy_bing 0.3.1 → 0.4.0

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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +1 -0
  4. data/README.md +18 -18
  5. data/Rakefile +1 -0
  6. data/lib/soapy_bing.rb +5 -1
  7. data/lib/soapy_bing/account.rb +1 -0
  8. data/lib/soapy_bing/accounts.rb +13 -17
  9. data/lib/soapy_bing/ads.rb +18 -41
  10. data/lib/soapy_bing/ads/campaign_performance_report.rb +49 -0
  11. data/lib/soapy_bing/ads/campaigns.rb +106 -0
  12. data/lib/soapy_bing/ads/parsers/bulk_csv_parser.rb +34 -0
  13. data/lib/soapy_bing/ads/parsers/report_csv_parser.rb +45 -0
  14. data/lib/soapy_bing/ads/report.rb +93 -0
  15. data/lib/soapy_bing/campaign_management.rb +21 -0
  16. data/lib/soapy_bing/country_codes.rb +17 -0
  17. data/lib/soapy_bing/country_codes.yml +727 -0
  18. data/lib/soapy_bing/customer.rb +1 -0
  19. data/lib/soapy_bing/helpers.rb +1 -1
  20. data/lib/soapy_bing/helpers/zip_downloader.rb +1 -0
  21. data/lib/soapy_bing/oauth_credentials.rb +1 -0
  22. data/lib/soapy_bing/param_guard.rb +1 -0
  23. data/lib/soapy_bing/service.rb +69 -0
  24. data/lib/soapy_bing/service_operation.rb +62 -0
  25. data/lib/soapy_bing/version.rb +2 -1
  26. data/lib/soapy_bing/wsdl/ad_insight.wsdl +1 -0
  27. data/lib/soapy_bing/wsdl/bulk.wsdl +1 -0
  28. data/lib/soapy_bing/wsdl/campaign_management.wsdl +1 -0
  29. data/lib/soapy_bing/wsdl/customer_billing.wsdl +1 -0
  30. data/lib/soapy_bing/wsdl/customer_management.wsdl +1 -0
  31. data/lib/soapy_bing/wsdl/reporting.wsdl +1 -0
  32. data/lib/tasks/console.rake +1 -0
  33. data/lib/tasks/coverage.rake +1 -0
  34. data/lib/tasks/spec.rake +1 -0
  35. data/lib/tasks/upate_wsdl_files.rake +23 -0
  36. data/lib/tasks/update_country_codes.rake +24 -0
  37. data/soapy_bing.gemspec +2 -1
  38. data/spec/fixtures/ads/campaign_performance_report.csv +37 -0
  39. data/spec/fixtures/ads/campaign_performance_report.json +170 -0
  40. data/spec/fixtures/ads/campaigns_by_account_id.csv +7 -0
  41. data/spec/fixtures/{bulk/campaigns.json → ads/campaigns_by_account_id.json} +496 -240
  42. data/spec/fixtures/ads/campaigns_by_campaign_ids.csv +5 -0
  43. data/spec/fixtures/ads/campaigns_by_campaign_ids.json +910 -0
  44. data/spec/fixtures/vcr_cassettes/SoapyBing_Accounts/_list/returns_a_list_of_SoapyBing_Account_objects.yml +27 -27
  45. data/spec/fixtures/vcr_cassettes/SoapyBing_Ads/_campaigns/by_account_id/returns_parsed_rows.yml +301 -0
  46. data/spec/fixtures/vcr_cassettes/SoapyBing_Ads/_campaigns/by_campaign_ids/returns_parsed_rows.yml +300 -0
  47. data/spec/fixtures/vcr_cassettes/SoapyBing_CampaignManagement/_get_geo_locations/returns_a_list_of_geo_locations_hashes.yml +154 -0
  48. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_pending_status.yml +65 -94
  49. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_empty_response.yml +511 -0
  50. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_response.yml +384 -0
  51. data/spec/integration/soapy_bing/accounts_spec.rb +1 -0
  52. data/spec/integration/soapy_bing/ads_campaigns_spec.rb +85 -0
  53. data/spec/integration/soapy_bing/campaign_management_spec.rb +22 -0
  54. data/spec/integration/soapy_bing/oauth_credentials_spec.rb +1 -0
  55. data/spec/simplecov_setup.rb +1 -0
  56. data/spec/soapy_bing/account_spec.rb +6 -2
  57. data/spec/soapy_bing/accounts_spec.rb +47 -0
  58. data/spec/soapy_bing/ads/campaign_performance_report_spec.rb +79 -0
  59. data/spec/soapy_bing/ads/campaigns_spec.rb +99 -0
  60. data/spec/soapy_bing/ads/{bulk/parsers/csv_parser_spec.rb → parsers/bulk_csv_parser_spec.rb} +5 -4
  61. data/spec/soapy_bing/ads/{reports/parsers/csv_parser_spec.rb → parsers/report_csv_parser_spec.rb} +6 -4
  62. data/spec/soapy_bing/country_codes_spec.rb +39 -0
  63. data/spec/soapy_bing/helpers/zip_downloader_spec.rb +2 -0
  64. data/spec/soapy_bing/oauth_credentials_spec.rb +6 -2
  65. data/spec/soapy_bing/param_guard_spec.rb +2 -0
  66. data/spec/soapy_bing/service_spec.rb +56 -0
  67. data/spec/spec_helper.rb +1 -0
  68. data/spec/support/dotenv.rb +1 -0
  69. data/spec/support/vcr.rb +75 -6
  70. metadata +74 -118
  71. data/lib/soapy_bing/ads/bulk.rb +0 -3
  72. data/lib/soapy_bing/ads/bulk/campaigns.rb +0 -65
  73. data/lib/soapy_bing/ads/bulk/parsers.rb +0 -2
  74. data/lib/soapy_bing/ads/bulk/parsers/csv_parser.rb +0 -35
  75. data/lib/soapy_bing/ads/reports.rb +0 -4
  76. data/lib/soapy_bing/ads/reports/base.rb +0 -74
  77. data/lib/soapy_bing/ads/reports/campaign_performance_report.rb +0 -24
  78. data/lib/soapy_bing/ads/reports/parsers.rb +0 -2
  79. data/lib/soapy_bing/ads/reports/parsers/csv_parser.rb +0 -46
  80. data/lib/soapy_bing/helpers/class_name.rb +0 -10
  81. data/lib/soapy_bing/soap.rb +0 -4
  82. data/lib/soapy_bing/soap/request.rb +0 -10
  83. data/lib/soapy_bing/soap/request/base.rb +0 -38
  84. data/lib/soapy_bing/soap/request/download_campaigns_by_account_ids_request.rb +0 -17
  85. data/lib/soapy_bing/soap/request/get_accounts_info_request.rb +0 -17
  86. data/lib/soapy_bing/soap/request/get_ad_groups_by_campaign_id_request.rb +0 -17
  87. data/lib/soapy_bing/soap/request/get_ads_by_ad_group_id_request.rb +0 -17
  88. data/lib/soapy_bing/soap/request/get_bulk_download_status_request.rb +0 -17
  89. data/lib/soapy_bing/soap/request/get_targets_by_campaign_ids_request.rb +0 -17
  90. data/lib/soapy_bing/soap/request/poll_generate_report_request.rb +0 -34
  91. data/lib/soapy_bing/soap/request/submit_generate_report_request.rb +0 -17
  92. data/lib/soapy_bing/soap/response.rb +0 -14
  93. data/lib/soapy_bing/soap/response/base.rb +0 -17
  94. data/lib/soapy_bing/soap/response/download_campaigns_by_account_ids_response.rb +0 -12
  95. data/lib/soapy_bing/soap/response/get_accounts_info_response.rb +0 -12
  96. data/lib/soapy_bing/soap/response/get_ad_groups_by_campaign_id_response.rb +0 -12
  97. data/lib/soapy_bing/soap/response/get_ads_by_ad_group_id_response.rb +0 -12
  98. data/lib/soapy_bing/soap/response/get_bulk_download_status_response.rb +0 -17
  99. data/lib/soapy_bing/soap/response/get_targets_by_campaign_ids_response.rb +0 -12
  100. data/lib/soapy_bing/soap/response/payload.rb +0 -28
  101. data/lib/soapy_bing/soap/response/poll_generate_report_response.rb +0 -14
  102. data/lib/soapy_bing/soap/response/report_status.rb +0 -34
  103. data/lib/soapy_bing/soap/response/submit_generate_report_response.rb +0 -12
  104. data/lib/soapy_bing/soap/template_renderer.rb +0 -22
  105. data/lib/soapy_bing/soap/templates/download_campaigns_by_account_ids.xml.erb +0 -22
  106. data/lib/soapy_bing/soap/templates/get_accounts_info.xml.erb +0 -12
  107. data/lib/soapy_bing/soap/templates/get_ad_groups_by_campaign_id.xml.erb +0 -15
  108. data/lib/soapy_bing/soap/templates/get_ads_by_ad_group_id.xml.erb +0 -15
  109. data/lib/soapy_bing/soap/templates/get_bulk_download_status.xml.erb +0 -15
  110. data/lib/soapy_bing/soap/templates/get_targets_by_campaign_ids.xml.erb +0 -20
  111. data/lib/soapy_bing/soap/templates/poll_generate_report.xml.erb +0 -18
  112. data/lib/soapy_bing/soap/templates/submit_generate_report.xml.erb +0 -46
  113. data/spec/fixtures/bulk/campaigns.csv +0 -8
  114. data/spec/fixtures/get_ad_groups_by_campaign_id.json +0 -587
  115. data/spec/fixtures/get_ads_by_ad_group_id.json +0 -218
  116. data/spec/fixtures/get_targets_by_campaign_ids.json +0 -81
  117. data/spec/fixtures/reports/campaign_performance_report.csv +0 -37
  118. data/spec/fixtures/reports/campaign_performance_report.json +0 -146
  119. data/spec/fixtures/soap_templates/simple.xml.erb +0 -2
  120. data/spec/fixtures/vcr_cassettes/SoapyBing_Ads/_get_ad_groups_by_campaign_id/1_1_1.yml +0 -150
  121. data/spec/fixtures/vcr_cassettes/SoapyBing_Ads/_get_ads_by_ad_group_id/1_2_1.yml +0 -141
  122. data/spec/fixtures/vcr_cassettes/SoapyBing_Ads/_get_targets_by_campaign_ids/1_3_1.yml +0 -111
  123. data/spec/fixtures/vcr_cassettes/SoapyBing_Ads_Bulk_Campaigns/_result_file_url/returns_result_file_url.yml +0 -216
  124. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_status.yml +0 -284
  125. data/spec/integration/soapy_bing/ads/bulk/campaigns_spec.rb +0 -35
  126. data/spec/integration/soapy_bing/ads/reports/campaign_performance_report_spec.rb +0 -40
  127. data/spec/integration/soapy_bing/ads_spec.rb +0 -32
  128. data/spec/soapy_bing/ads/reports/campaign_performance_report_spec.rb +0 -42
  129. data/spec/soapy_bing/ads_spec.rb +0 -33
  130. data/spec/soapy_bing/helpers/class_name_spec.rb +0 -15
  131. data/spec/soapy_bing/soap/request/base_spec.rb +0 -55
  132. data/spec/soapy_bing/soap/request/poll_generate_report_request_spec.rb +0 -60
  133. data/spec/soapy_bing/soap/response/base_spec.rb +0 -13
  134. data/spec/soapy_bing/soap/response/get_bulk_download_status_response_spec.rb +0 -60
  135. data/spec/soapy_bing/soap/response/payload_spec.rb +0 -48
  136. data/spec/soapy_bing/soap/response/poll_generate_report_response_spec.rb +0 -26
  137. data/spec/soapy_bing/soap/response/report_status_spec.rb +0 -92
  138. data/spec/soapy_bing/soap/response/submit_generate_report_response_spec.rb +0 -20
  139. data/spec/soapy_bing/soap/template_renderer_spec.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6769b40c74197462ce64b3858e1d9685a96a2afc
4
- data.tar.gz: e7cd2d208675f7318e838318e9ba3f7598a95d2c
3
+ metadata.gz: 6516951a384d76b68b51cbd390f1d66fb7b41f8d
4
+ data.tar.gz: aa5436b3258ebfda01f6b832782242f0d6c39ee4
5
5
  SHA512:
6
- metadata.gz: 9d9c735dccb584cc54a9de1a4827a481393f24a4ab7e086c0e3ac9770d9c0d272fbedda8d8da233e2fcb8a78132663be31a3c58be26c36e1ee5525dfb1e21990
7
- data.tar.gz: 9c19239300bc2ee50a79a3c87cdb995d24a7e37b7e029c976cc735ce7683088faed872e76f383ba9397d76b46df3175a9fa36ef40ed696cd1d6347a82a9b6aec
6
+ metadata.gz: 4472f06bcc20a6520359a9542d4361e61969214373c40bfbf909d93751de2b7e584d082aa31cadcb94f7dea6fade3d17f03f31d96849117971dd627cd69981b1
7
+ data.tar.gz: 2c7a31b9d70b3b3c6d82c1532566e456d546b0ef48875773eda984ce80002c1c62bfb8dadea6c54a02176edc0fd99829e04beca9f903461f1ee92287b2e832d3
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.3.4
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  source 'https://rubygems.org'
3
4
 
4
5
  gemspec
data/README.md CHANGED
@@ -8,41 +8,41 @@ Client library for [Bing Ads API](https://msdn.microsoft.com/en-us/library/bing-
8
8
  In your Gemfile
9
9
 
10
10
  ```ruby
11
- gem 'soapy_bing', git: 'https://github.com/ad2games/soapy_bing'
11
+ gem 'soapy_bing'
12
12
  ```
13
13
 
14
14
  ## Usage Examples
15
15
 
16
16
  ```ruby
17
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
-
18
+ rows = bing_ads.campaign_performance_report(
19
+ date_start: '2017-05-14',
20
+ date_end: '2017-05-14',
22
21
  settings: { # optional
23
- columns: [''] # see SoapyBing::Ads::Report::Base::DEFAULT_REPORT_SETTINGS[:columns]
22
+ columns: [''] # see SoapyBing::Report::Base::DEFAULT_REPORT_SETTINGS[:columns]
24
23
  }
25
24
  )
26
- report.rows # =>
27
- # [{
28
- # "HourOfDay" => "0",
29
- # "CampaignName" => "My Campaign",
30
- # "Impressions" => "100",
31
- # "Clicks" => "47",
32
- # "Spend" => "12.65"
33
- # }, {
34
- # ...
35
- # }]
25
+ # =>
26
+ # [{
27
+ # "GregorianDate" => "2017-05-14"
28
+ # "Hour" => "0",
29
+ # "CampaignName" => "My Campaign",
30
+ # "Impressions" => "100",
31
+ # "Clicks" => "47",
32
+ # "Spend" => "12.65"
33
+ # }, {
34
+ # ...
35
+ # }]
36
36
  ```
37
37
 
38
38
  ## Authentication
39
39
 
40
40
  Authentication attributes could be passed explicitly when new instance of SoapyBing::Ads is created.
41
- Or they could be configured as Envronment variables.
41
+ Or they could be configured as Environment variables.
42
42
 
43
43
  ```ruby
44
44
  SoapyBing::Ads.new(
45
- oauth: { # optional, could be configured via environment variables as
45
+ oauth_credentials: { # optional, could be configured via environment variables as
46
46
  client_id: '', # ENV['BING_ADS_OAUTH_CLIENT_ID']
47
47
  client_secret: '', # ENV['BING_ADS_OAUTH_CLIENT_SECRET']
48
48
  refresh_token: '' # ENV['BING_ADS_OAUTH_REFRESH_TOKEN']
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'rubygems'
3
4
  require 'bundler/setup'
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'httparty'
3
4
  require 'retryable'
4
5
  require 'active_support'
@@ -9,6 +10,9 @@ require 'soapy_bing/param_guard'
9
10
  require 'soapy_bing/customer'
10
11
  require 'soapy_bing/account'
11
12
  require 'soapy_bing/oauth_credentials'
12
- require 'soapy_bing/soap'
13
13
  require 'soapy_bing/ads'
14
14
  require 'soapy_bing/accounts'
15
+ require 'soapy_bing/service'
16
+ require 'soapy_bing/service_operation'
17
+ require 'soapy_bing/campaign_management'
18
+ require 'soapy_bing/country_codes'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module SoapyBing
3
4
  class Account
4
5
  attr_reader :developer_token, :account_id, :customer_id
@@ -1,32 +1,28 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module SoapyBing
3
4
  class Accounts
4
- attr_reader :oauth_credentials, :customer
5
+ attr_reader :service
5
6
 
6
- def initialize(oauth: {}, customer: {})
7
- @oauth_credentials = OauthCredentials.new(oauth)
8
- @customer = Customer.new(customer)
7
+ def initialize(*args)
8
+ @service = Service.customer_management(*args)
9
9
  end
10
10
 
11
11
  def list
12
- do_request(Soap::Request::GetAccountsInfoRequest).map do |account|
13
- Account.new(
14
- developer_token: customer.developer_token,
15
- customer_id: customer.customer_id,
16
- account_id: account['Id']
17
- )
12
+ response = service.get_accounts_info
13
+ response[:accounts_info][:account_info].map do |account_info|
14
+ build_account(account_info)
18
15
  end
19
16
  end
20
17
 
21
18
  private
22
19
 
23
- def do_request(klass, options = {})
24
- klass.new(context: {
25
- authentication_token: oauth_credentials.access_token,
26
- developer_token: customer.developer_token
27
- }.merge(options))
28
- .perform
29
- .payload
20
+ def build_account(account_info)
21
+ Account.new(
22
+ developer_token: service.customer.developer_token,
23
+ customer_id: service.customer.customer_id,
24
+ account_id: account_info[:id]
25
+ )
30
26
  end
31
27
  end
32
28
  end
@@ -1,56 +1,33 @@
1
1
  # frozen_string_literal: true
2
- require 'soapy_bing/ads/reports'
3
- require 'soapy_bing/ads/bulk'
2
+
3
+ require 'soapy_bing/ads/campaigns'
4
+ require 'soapy_bing/ads/campaign_performance_report'
4
5
 
5
6
  module SoapyBing
6
7
  class Ads
7
- attr_reader :oauth_credentials, :account
8
+ attr_reader :service_options
8
9
 
9
- def initialize(oauth: {}, account: {})
10
- @oauth_credentials = OauthCredentials.new(oauth)
11
- @account = Account.new(account)
10
+ def initialize(service_options = {})
11
+ @service_options = service_options
12
12
  end
13
13
 
14
- def campaign_performance_report(date_start:, date_end:, settings: {})
15
- Reports::CampaignPerformanceReport.new(
16
- oauth_credentials: oauth_credentials,
17
- account: account,
14
+ def campaign_performance_report(date_start:, date_end:, settings: {}, polling_settings: {})
15
+ CampaignPerformanceReport.new(
16
+ service_options: service_options,
18
17
  date_start: date_start,
19
18
  date_end: date_end,
20
- settings: settings
21
- )
22
- end
23
-
24
- def bulk_campaigns(entities = nil, polling_settings = {})
25
- Bulk::Campaigns.new(
26
- oauth_credentials: oauth_credentials,
27
- account: account,
28
- entities: entities,
19
+ settings: settings,
29
20
  polling_settings: polling_settings
30
- )
31
- end
32
-
33
- def get_ad_groups_by_campaign_id(campaign_id)
34
- do_request(Soap::Request::GetAdGroupsByCampaignIdRequest, campaign_id: campaign_id)
21
+ ).rows
35
22
  end
36
23
 
37
- def get_ads_by_ad_group_id(ad_group_id)
38
- do_request(Soap::Request::GetAdsByAdGroupIdRequest, ad_group_id: ad_group_id)
39
- end
40
-
41
- def get_targets_by_campaign_ids(campaign_ids)
42
- do_request(Soap::Request::GetTargetsByCampaignIdsRequest, campaign_ids: campaign_ids)
43
- end
44
-
45
- private
46
-
47
- def do_request(klass, options)
48
- klass.new(context: {
49
- account: account,
50
- authentication_token: oauth_credentials.access_token
51
- }.merge(options))
52
- .perform
53
- .payload
24
+ def campaigns(entities = Campaigns::DEFAULT_ENTITIES, polling_settings: {}, campaign_ids: nil)
25
+ Campaigns.new(
26
+ service_options: service_options,
27
+ entities: entities,
28
+ polling_settings: polling_settings,
29
+ campaign_ids: campaign_ids
30
+ ).rows
54
31
  end
55
32
  end
56
33
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'soapy_bing/ads/report'
4
+ require 'date'
5
+
6
+ module SoapyBing
7
+ class Ads
8
+ class CampaignPerformanceReport < Report
9
+ attr_reader :date_start, :date_end
10
+
11
+ def initialize(options)
12
+ super
13
+ @date_start = Date.parse(options.fetch(:date_start))
14
+ @date_end = Date.parse(options.fetch(:date_end))
15
+ end
16
+
17
+ def message
18
+ {
19
+ aggregation: settings.aggregation,
20
+ columns: { campaign_performance_report_column: settings.columns },
21
+ scope: scope,
22
+ time: {
23
+ custom_date_range_end: date_hash(date_end),
24
+ custom_date_range_start: date_hash(date_start)
25
+ }
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def scope
32
+ {
33
+ account_ids: {
34
+ '@xmlns:a1' => 'http://schemas.microsoft.com/2003/10/Serialization/Arrays',
35
+ 'a1:long' => service.account.account_id
36
+ }
37
+ }
38
+ end
39
+
40
+ def date_hash(date)
41
+ { day: date.day, month: date.month, year: date.year }
42
+ end
43
+
44
+ def request_type
45
+ 'CampaignPerformanceReportRequest'
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'soapy_bing/ads/parsers/bulk_csv_parser'
4
+
5
+ module SoapyBing
6
+ class Ads
7
+ class Campaigns
8
+ DEFAULT_ENTITIES = %w[CampaignTargetCriterions Ads].freeze
9
+ DEFAULT_POLLING_SETTINGS = {
10
+ tries: 20,
11
+ sleep: ->(n) { n < 7 ? 2**n : 120 }
12
+ }.freeze
13
+ NotCompleted = Class.new(StandardError)
14
+ StatusFailed = Class.new(StandardError)
15
+
16
+ attr_reader :service, :entities, :polling_settings, :status, :campaign_ids
17
+
18
+ def initialize(
19
+ service_options:,
20
+ entities: DEFAULT_ENTITIES,
21
+ polling_settings: {},
22
+ campaign_ids: nil
23
+ )
24
+ @service = Service.bulk(service_options)
25
+ @entities = entities
26
+ @polling_settings = DEFAULT_POLLING_SETTINGS.merge(polling_settings)
27
+ @campaign_ids = campaign_ids
28
+ end
29
+
30
+ def rows
31
+ @rows ||= Parsers::BulkCsvParser.new(Helpers::ZipDownloader.new(result_file_url).read).rows
32
+ end
33
+
34
+ private
35
+
36
+ def result_file_url
37
+ @result_file_url ||= begin
38
+ wait_status_complete
39
+ status[:result_file_url]
40
+ end
41
+ end
42
+
43
+ def fetch_status
44
+ response = service.get_bulk_download_status(request_id: download_request_id)
45
+ if response[:request_status] == 'Failed'
46
+ raise StatusFailed, response[:errors].to_s
47
+ end
48
+ @status = response.slice(:percent_complete, :request_status, :result_file_url)
49
+ end
50
+
51
+ def wait_status_complete
52
+ Retryable.retryable(polling_settings.merge(on: NotCompleted)) do
53
+ fetch_status
54
+ raise NotCompleted if status[:request_status] != 'Completed'
55
+ end
56
+ end
57
+
58
+ def download_request_id
59
+ @download_request_id ||= download_campaigns
60
+ end
61
+
62
+ def download_campaigns
63
+ response = campaign_ids ? by_campaign_ids : by_account_ids
64
+ response[:download_request_id]
65
+ end
66
+
67
+ def by_campaign_ids
68
+ service.download_campaigns_by_campaign_ids(
69
+ {
70
+ campaigns: {
71
+ campaign_scope: campaign_scope
72
+ }
73
+ }.merge(common_request_body)
74
+ )
75
+ end
76
+
77
+ def campaign_scope
78
+ campaign_ids.map do |campaign_id|
79
+ {
80
+ campaign_id: campaign_id,
81
+ parent_account_id: service.account.account_id
82
+ }
83
+ end
84
+ end
85
+
86
+ def by_account_ids
87
+ service.download_campaigns_by_account_ids(
88
+ {
89
+ account_ids: {
90
+ '@xmlns:a1' => 'http://schemas.microsoft.com/2003/10/Serialization/Arrays',
91
+ 'a1:long' => service.account.account_id
92
+ }
93
+ }.merge(common_request_body)
94
+ )
95
+ end
96
+
97
+ def common_request_body
98
+ {
99
+ download_entities: { download_entity: entities },
100
+ download_file_type: 'Csv',
101
+ format_version: '5.0'
102
+ }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+
5
+ module SoapyBing
6
+ class Ads
7
+ module Parsers
8
+ class BulkCsvParser
9
+ def initialize(raw)
10
+ @raw = raw
11
+ end
12
+
13
+ def rows
14
+ @rows ||= begin
15
+ header, *body = extract_csv_payload
16
+ body.map { |row| header.zip(row).to_h }
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :raw
23
+
24
+ def extract_csv_payload
25
+ text = raw.dup
26
+ text.force_encoding(Encoding::UTF_8).encode! unless text.encoding == Encoding::UTF_8
27
+ text.sub!(/^\xEF\xBB\xBF/, '') # cleanup BOM
28
+
29
+ CSV.parse(text)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+
5
+ module SoapyBing
6
+ class Ads
7
+ module Parsers
8
+ class ReportCsvParser
9
+ class FormatError < StandardError; end
10
+
11
+ CSV_PAYLOAD_OFFSET_FRONT = 10 # First 10 csv lines are report metadata
12
+ CSV_PAYLOAD_OFFSET_BACK = 2 # Last 2 csv lines are Microsoft copyright
13
+
14
+ def initialize(raw)
15
+ @raw = raw
16
+ end
17
+
18
+ def rows
19
+ @rows ||= begin
20
+ header, *body = extract_csv_payload
21
+ raise FormatError if body.size != payload_rows_number
22
+ body.map { |row| header.zip(row).to_h }
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :raw
29
+
30
+ def extract_csv_payload
31
+ text = raw.dup
32
+ text.force_encoding(Encoding::UTF_8).encode! unless text.encoding == Encoding::UTF_8
33
+ text.sub!(/^\xEF\xBB\xBF/, '') # cleanup BOM
34
+
35
+ csv_rows = CSV.parse(text)
36
+ csv_rows[CSV_PAYLOAD_OFFSET_FRONT...-CSV_PAYLOAD_OFFSET_BACK]
37
+ end
38
+
39
+ def payload_rows_number
40
+ raw.match(/"Rows: (\d+)"/)[1].to_i
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end