twitter-ads 5.2.0 → 8.0.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -2
  3. data/README.md +1 -1
  4. data/lib/twitter-ads.rb +8 -4
  5. data/lib/twitter-ads/account.rb +6 -24
  6. data/lib/twitter-ads/audiences/tailored_audience.rb +56 -1
  7. data/lib/twitter-ads/{targeting_criteria/behavior_taxonomy.rb → campaign/advertiser_business_categories.rb} +6 -6
  8. data/lib/twitter-ads/campaign/campaign.rb +1 -2
  9. data/lib/twitter-ads/campaign/content_categories.rb +23 -0
  10. data/lib/twitter-ads/campaign/funding_instrument.rb +1 -2
  11. data/lib/twitter-ads/campaign/line_item.rb +4 -4
  12. data/lib/twitter-ads/campaign/organic_tweet.rb +1 -3
  13. data/lib/twitter-ads/campaign/targeting_criteria.rb +1 -1
  14. data/lib/twitter-ads/campaign/tweet.rb +4 -49
  15. data/lib/twitter-ads/client.rb +2 -2
  16. data/lib/twitter-ads/creative/account_media.rb +4 -6
  17. data/lib/twitter-ads/creative/draft_tweet.rb +40 -0
  18. data/lib/twitter-ads/creative/image_app_download_card.rb +2 -2
  19. data/lib/twitter-ads/creative/image_conversation_card.rb +3 -2
  20. data/lib/twitter-ads/creative/media_creative.rb +2 -3
  21. data/lib/twitter-ads/creative/media_library.rb +12 -13
  22. data/lib/twitter-ads/creative/promoted_account.rb +1 -2
  23. data/lib/twitter-ads/creative/promoted_tweet.rb +2 -3
  24. data/lib/twitter-ads/creative/scheduled_tweet.rb +1 -12
  25. data/lib/twitter-ads/creative/tweets.rb +52 -0
  26. data/lib/twitter-ads/creative/video_app_download_card.rb +4 -6
  27. data/lib/twitter-ads/creative/video_conversation_card.rb +6 -6
  28. data/lib/twitter-ads/creative/video_website_card.rb +3 -5
  29. data/lib/twitter-ads/creative/website_card.rb +2 -2
  30. data/lib/twitter-ads/cursor.rb +6 -0
  31. data/lib/twitter-ads/enum.rb +22 -13
  32. data/lib/twitter-ads/error.rb +5 -15
  33. data/lib/twitter-ads/http/request.rb +37 -2
  34. data/lib/twitter-ads/http/response.rb +1 -13
  35. data/lib/twitter-ads/resources/analytics.rb +99 -47
  36. data/lib/twitter-ads/resources/dsl.rb +8 -1
  37. data/lib/twitter-ads/restapi.rb +29 -0
  38. data/lib/twitter-ads/settings/tax.rb +13 -1
  39. data/lib/twitter-ads/targeting/audience_summary.rb +47 -0
  40. data/lib/twitter-ads/targeting_criteria/{behavior.rb → conversation.rb} +3 -7
  41. data/lib/twitter-ads/targeting_criteria/event.rb +1 -0
  42. data/lib/twitter-ads/utils.rb +23 -0
  43. data/lib/twitter-ads/version.rb +1 -1
  44. data/spec/fixtures/audience_summary.json +14 -0
  45. data/spec/fixtures/line_items_all.json +2 -10
  46. data/spec/fixtures/line_items_load.json +0 -1
  47. data/spec/fixtures/tailored_audiences_all.json +3 -0
  48. data/spec/fixtures/targeted_audiences.json +33 -0
  49. data/spec/fixtures/tweet_previews.json +23 -0
  50. data/spec/spec_helper.rb +1 -4
  51. data/spec/twitter-ads/audiences/tailored_audience_spec.rb +25 -2
  52. data/spec/twitter-ads/campaign/line_item_spec.rb +0 -1
  53. data/spec/twitter-ads/campaign/targeting_criteria_spec.rb +1 -1
  54. data/spec/twitter-ads/campaign/tweet_spec.rb +0 -59
  55. data/spec/twitter-ads/client_spec.rb +17 -1
  56. data/spec/twitter-ads/creative/media_creative_spec.rb +1 -1
  57. data/spec/twitter-ads/creative/promoted_tweet_spec.rb +18 -0
  58. data/spec/twitter-ads/creative/tweet_previews_spec.rb +41 -0
  59. data/spec/twitter-ads/rate_limit_spec.rb +247 -0
  60. data/spec/twitter-ads/retry_count_spec.rb +61 -0
  61. data/spec/twitter-ads/{creative/image_app_download_card_spec.rb → targeting/audience_summary_spec.rb} +16 -18
  62. metadata +50 -49
  63. data/lib/twitter-ads/audiences/audience_intelligence.rb +0 -68
  64. data/lib/twitter-ads/targeting/reach_estimate.rb +0 -78
  65. data/spec/fixtures/tweet_preview.json +0 -24
  66. data/spec/twitter-ads/campaign/reach_estimate_spec.rb +0 -103
  67. data/spec/twitter-ads/creative/account_media_spec.rb +0 -32
  68. data/spec/twitter-ads/creative/image_conversation_card_spec.rb +0 -40
  69. data/spec/twitter-ads/creative/video_app_download_card_spec.rb +0 -42
  70. data/spec/twitter-ads/creative/video_conversation_card_spec.rb +0 -51
  71. data/spec/twitter-ads/creative/website_card_spec.rb +0 -42
@@ -55,10 +55,26 @@ describe TwitterAds::Client do
55
55
 
56
56
  it 'allows additional options' do
57
57
  expect {
58
- Client.new(consumer_key, consumer_secret, access_token, {})
58
+ Client.new(consumer_key, consumer_secret, access_token, access_token_secret, options: {})
59
59
  }.not_to raise_error
60
60
  end
61
61
 
62
+ it 'test client options' do
63
+ client = Client.new(
64
+ consumer_key,
65
+ consumer_secret,
66
+ access_token,
67
+ access_token_secret,
68
+ options: {
69
+ handle_rate_limit: true,
70
+ retry_max: 1,
71
+ retry_delay: 3000,
72
+ retry_on_status: [404, 500, 503]
73
+ }
74
+ )
75
+ expect(client.options.length).to eq 4
76
+ end
77
+
62
78
  end
63
79
 
64
80
  describe '#inspect' do
@@ -25,7 +25,7 @@ describe TwitterAds::Creative::MediaCreative do
25
25
 
26
26
  # check model properties
27
27
  subject { described_class.new(account) }
28
- read = %w(id created_at updated_at deleted approval_status serving_status)
28
+ read = %w(id created_at updated_at deleted approval_status entity_status)
29
29
  write = %w(account_media_id line_item_id landing_url)
30
30
  include_examples 'object property check', read, write
31
31
 
@@ -41,6 +41,24 @@ describe TwitterAds::Creative::PromotedTweet do
41
41
  expect { subject.save }.to raise_error(TwitterAds::ClientError)
42
42
  end
43
43
 
44
+ it 'sets params[:tweet_ids] from params[:tweet_id]' do
45
+ request = double('request')
46
+ response = double('response')
47
+ allow(response).to receive(:body).and_return({ data: [{}] })
48
+ allow(request).to receive(:perform).and_return(response)
49
+ expected_params = { params: { line_item_id: '12345', tweet_ids: 99999999999999999999 } }
50
+
51
+ expect(Request).to receive(:new).with(
52
+ client,
53
+ :post,
54
+ "/#{TwitterAds::API_VERSION}/accounts/#{account.id}/promoted_tweets",
55
+ expected_params
56
+ ).and_return(request)
57
+
58
+ subject.line_item_id = '12345'
59
+ subject.tweet_id = 99999999999999999999
60
+ subject.save
61
+ end
44
62
  end
45
63
 
46
64
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2019 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ include TwitterAds::Enum
7
+
8
+ describe TwitterAds::Creative::TweetPreview do
9
+
10
+ let!(:resource) { "#{ADS_API}/accounts/2iqph/tweet_previews" }
11
+
12
+ before(:each) do
13
+ stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
14
+ stub_fixture(:get, :tweet_previews, /#{resource}\?.*/)
15
+ end
16
+
17
+ let(:client) do
18
+ Client.new(
19
+ Faker::Lorem.characters(40),
20
+ Faker::Lorem.characters(40),
21
+ Faker::Lorem.characters(40),
22
+ Faker::Lorem.characters(40)
23
+ )
24
+ end
25
+
26
+ let(:account) { client.accounts('2iqph') }
27
+ let(:instance) { described_class.new(account) }
28
+
29
+ it 'inspect TweetPreview.load() response' do
30
+ preview = instance.load(
31
+ account,
32
+ tweet_ids: %w(1130942781109596160 1101254234031370240),
33
+ tweet_type: TweetType::PUBLISHED)
34
+
35
+ expect(preview).to be_instance_of(Cursor)
36
+ expect(preview.count).to eq 2
37
+ tweet = preview.first
38
+ expect(tweet.tweet_id).to eq '1130942781109596160'
39
+ end
40
+
41
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2019 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ describe TwitterAds::Campaign do
7
+
8
+ before(:each) do
9
+ allow_any_instance_of(Object).to receive(:sleep)
10
+ stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
11
+ end
12
+
13
+ let(:client) do
14
+ Client.new(
15
+ Faker::Lorem.characters(40),
16
+ Faker::Lorem.characters(40),
17
+ Faker::Lorem.characters(40),
18
+ Faker::Lorem.characters(40)
19
+ )
20
+ end
21
+ let(:account) { client_simple.accounts('2iqph') }
22
+
23
+ let(:client_simple) do
24
+ Client.new(
25
+ Faker::Lorem.characters(40),
26
+ Faker::Lorem.characters(40),
27
+ Faker::Lorem.characters(40),
28
+ Faker::Lorem.characters(40),
29
+ options: {
30
+ handle_rate_limit: true
31
+ }
32
+ )
33
+ end
34
+ let(:account1) { client_simple.accounts('2iqph') }
35
+
36
+ let(:client_combine) do
37
+ Client.new(
38
+ Faker::Lorem.characters(40),
39
+ Faker::Lorem.characters(40),
40
+ Faker::Lorem.characters(40),
41
+ Faker::Lorem.characters(40),
42
+ options: {
43
+ handle_rate_limit: true,
44
+ retry_max: 1,
45
+ retry_delay: 3000,
46
+ retry_on_status: [500]
47
+ }
48
+ )
49
+ end
50
+ let(:account2) { client_combine.accounts('2iqph') }
51
+
52
+ it 'test rate-limit handle - success' do
53
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
54
+ {
55
+ body: fixture(:campaigns_all),
56
+ status: 429,
57
+ headers: {
58
+ 'x-account-rate-limit-limit': 10000,
59
+ 'x-account-rate-limit-remaining': 9999,
60
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
61
+ }
62
+ },
63
+ {
64
+ body: fixture(:campaigns_all),
65
+ status: 200
66
+ }
67
+ )
68
+ cusor = described_class.all(account1)
69
+ expect(cusor).to be_instance_of(Cursor)
70
+ expect(stub).to have_been_requested.times(2)
71
+ end
72
+
73
+ it 'test rate-limit handle - fail' do
74
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
75
+ {
76
+ body: fixture(:campaigns_all),
77
+ status: 429,
78
+ headers: {
79
+ 'x-account-rate-limit-limit': 10000,
80
+ 'x-account-rate-limit-remaining': 9999,
81
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
82
+ }
83
+ },
84
+ {
85
+ body: fixture(:campaigns_all),
86
+ status: 429,
87
+ headers: {
88
+ 'x-account-rate-limit-limit': 10000,
89
+ 'x-account-rate-limit-remaining': 9999,
90
+ 'x-account-rate-limit-reset': 4102444800
91
+ }
92
+ }
93
+ )
94
+ begin
95
+ cusor = described_class.all(account1)
96
+ rescue RateLimit => e
97
+ cusor = e
98
+ end
99
+ expect(cusor).to be_instance_of(RateLimit)
100
+ expect(stub).to have_been_requested.times(2)
101
+ expect(cusor.reset_at).to eq 4102444800
102
+ end
103
+
104
+ it 'test rate-limit handle with retry - case 1' do
105
+ # scenario:
106
+ # - 500 (retry) -> 429 (handle rate limit) -> 200 (end)
107
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
108
+ {
109
+ body: fixture(:campaigns_all),
110
+ status: 500,
111
+ headers: {
112
+ 'x-account-rate-limit-limit': 10000,
113
+ 'x-account-rate-limit-remaining': 9999,
114
+ 'x-account-rate-limit-reset': 4102444800
115
+ }
116
+ },
117
+ {
118
+ body: fixture(:campaigns_all),
119
+ status: 429,
120
+ headers: {
121
+ 'x-account-rate-limit-limit': 10000,
122
+ 'x-account-rate-limit-remaining': 9999,
123
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
124
+ }
125
+ },
126
+ {
127
+ body: fixture(:campaigns_all),
128
+ status: 200
129
+ }
130
+ )
131
+ cusor = described_class.all(account2)
132
+ expect(cusor).to be_instance_of(Cursor)
133
+ expect(stub).to have_been_requested.times(3)
134
+ end
135
+
136
+ it 'test rate-limit handle with retry - case 2' do
137
+ # scenario:
138
+ # - 429 (handle rate limit) -> 500 (retry) -> 200 (end)
139
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
140
+ {
141
+ body: fixture(:campaigns_all),
142
+ status: 429,
143
+ headers: {
144
+ 'x-account-rate-limit-limit': 10000,
145
+ 'x-account-rate-limit-remaining': 9999,
146
+ 'x-account-rate-limit-reset': Time.now.to_i + 5
147
+ }
148
+ },
149
+ {
150
+ body: fixture(:campaigns_all),
151
+ status: 500,
152
+ headers: {
153
+ 'x-account-rate-limit-limit': 10000,
154
+ 'x-account-rate-limit-remaining': 9999,
155
+ 'x-account-rate-limit-reset': 4102444800
156
+ }
157
+ },
158
+ {
159
+ body: fixture(:campaigns_all),
160
+ status: 200
161
+ }
162
+ )
163
+ cusor = described_class.all(account2)
164
+ expect(cusor).to be_instance_of(Cursor)
165
+ expect(stub).to have_been_requested.times(3)
166
+ end
167
+
168
+ it 'test rate-limit header access from cusor class' do
169
+ stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
170
+ {
171
+ body: fixture(:campaigns_all),
172
+ status: 200,
173
+ headers: {
174
+ 'x-account-rate-limit-limit': 10000,
175
+ 'x-account-rate-limit-remaining': 9999,
176
+ 'x-account-rate-limit-reset': 4102444800
177
+ }
178
+ }
179
+ )
180
+ cusor = described_class.all(account)
181
+ expect(cusor).to be_instance_of(Cursor)
182
+ expect(cusor.account_rate_limit_limit).to eq 10000
183
+ expect(cusor.account_rate_limit_remaining).to eq 9999
184
+ expect(cusor.account_rate_limit_reset).to eq 4102444800
185
+ end
186
+
187
+ it 'test rate-limit header access from resource class' do
188
+ stub_request(:any, /accounts\/2iqph\/campaigns\/2wap7/).to_return(
189
+ {
190
+ body: fixture(:campaigns_load),
191
+ status: 200,
192
+ headers: {
193
+ 'x-account-rate-limit-limit': 10000,
194
+ 'x-account-rate-limit-remaining': 9999,
195
+ 'x-account-rate-limit-reset': 4102444800
196
+ }
197
+ }
198
+ )
199
+
200
+ campaign = described_class.load(account, '2wap7')
201
+ resource = "/#{TwitterAds::API_VERSION}/accounts/2iqph/campaigns/2wap7"
202
+ params = {}
203
+ response = TwitterAds::Request.new(client, :get, resource, params: params).perform
204
+ data = campaign.from_response(response.body[:data], response.headers)
205
+ expect(data).to be_instance_of(Campaign)
206
+ expect(data.account_rate_limit_limit).to eq 10000
207
+ expect(data.account_rate_limit_remaining).to eq 9999
208
+ expect(data.account_rate_limit_reset).to eq 4102444800
209
+ end
210
+
211
+ it 'test rate-limit header access check instance variables not conflicting' do
212
+ stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
213
+ {
214
+ body: fixture(:campaigns_all),
215
+ status: 200,
216
+ headers: {
217
+ 'x-account-rate-limit-limit': 10000,
218
+ 'x-account-rate-limit-remaining': 9999,
219
+ 'x-account-rate-limit-reset': 4102444800
220
+ }
221
+ },
222
+ {
223
+ body: fixture(:campaigns_all),
224
+ status: 200,
225
+ headers: {
226
+ 'x-account-rate-limit-limit': 10000,
227
+ 'x-account-rate-limit-remaining': 9998,
228
+ 'x-account-rate-limit-reset': 4102444800
229
+ }
230
+ }
231
+ )
232
+
233
+ campaign_first = described_class.all(account)
234
+ campaign_second = described_class.all(account)
235
+
236
+ expect(campaign_first).to be_instance_of(Cursor)
237
+ expect(campaign_first.account_rate_limit_limit).to eq 10000
238
+ expect(campaign_first.account_rate_limit_remaining).to eq 9999
239
+ expect(campaign_first.account_rate_limit_reset).to eq 4102444800
240
+
241
+ expect(campaign_second).to be_instance_of(Cursor)
242
+ expect(campaign_second.account_rate_limit_limit).to eq 10000
243
+ expect(campaign_second.account_rate_limit_remaining).to eq 9998
244
+ expect(campaign_second.account_rate_limit_reset).to eq 4102444800
245
+ end
246
+
247
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2019 Twitter, Inc.
3
+
4
+ require 'spec_helper'
5
+
6
+ describe TwitterAds::Campaign do
7
+
8
+ before(:each) do
9
+ allow_any_instance_of(Object).to receive(:sleep)
10
+ stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
11
+ end
12
+
13
+ let(:client) do
14
+ Client.new(
15
+ Faker::Lorem.characters(40),
16
+ Faker::Lorem.characters(40),
17
+ Faker::Lorem.characters(40),
18
+ Faker::Lorem.characters(40),
19
+ options: {
20
+ retry_max: 1,
21
+ retry_delay: 3000,
22
+ retry_on_status: [404, 500, 503]
23
+ }
24
+ )
25
+ end
26
+ let(:account) { client.accounts('2iqph') }
27
+
28
+ it 'test retry count - success' do
29
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
30
+ {
31
+ body: fixture(:campaigns_all),
32
+ status: 404
33
+ },
34
+ {
35
+ body: fixture(:campaigns_all),
36
+ status: 200
37
+ }
38
+ )
39
+ cusor = described_class.all(account)
40
+ expect(cusor).to be_instance_of(Cursor)
41
+ expect(stub).to have_been_requested.times(2)
42
+ end
43
+
44
+ it 'test retry count - fail' do
45
+ stub = stub_request(:get, "#{ADS_API}/accounts/2iqph/campaigns").to_return(
46
+ {
47
+ body: fixture(:campaigns_all),
48
+ status: 404
49
+ }
50
+ ).times(2)
51
+
52
+ begin
53
+ cusor = described_class.all(account)
54
+ rescue NotFound => e
55
+ cusor = e
56
+ end
57
+ expect(cusor).to be_instance_of(NotFound)
58
+ expect(stub).to have_been_requested.times(2)
59
+ end
60
+
61
+ end
@@ -3,11 +3,12 @@
3
3
 
4
4
  require 'spec_helper'
5
5
 
6
- describe TwitterAds::Creative::ImageAppDownloadCard do
6
+ describe TwitterAds::AudienceSummary do
7
7
 
8
8
  before(:each) do
9
9
  stub_fixture(:get, :accounts_all, "#{ADS_API}/accounts")
10
10
  stub_fixture(:get, :accounts_load, "#{ADS_API}/accounts/2iqph")
11
+ stub_fixture(:post, :audience_summary, "#{ADS_API}/accounts/2iqph/audience_summary")
11
12
  end
12
13
 
13
14
  let(:client) do
@@ -20,24 +21,21 @@ describe TwitterAds::Creative::ImageAppDownloadCard do
20
21
  end
21
22
 
22
23
  let(:account) { client.accounts.first }
24
+ let(:params) {
25
+ {
26
+ targeting_criteria: [{
27
+ targeting_type: 'LOCATION',
28
+ targeting_value: '96683cc9126741d1'
29
+ }]
30
+ }
31
+ }
23
32
 
24
33
  # check model properties
25
- subject { described_class.new(account) }
26
- read = %w(id created_at updated_at deleted)
27
-
28
- write = %w(
29
- name
30
- country_code
31
- iphone_app_id
32
- iphone_deep_link
33
- ipad_app_id
34
- ipad_deep_link
35
- googleplay_app_id
36
- googleplay_deep_link
37
- app_cta
38
- wide_app_image_media_id
39
- )
40
-
41
- include_examples 'object property check', read, write
34
+ subject { described_class.fetch(account, params) }
35
+
36
+ it 'has all the correct properties' do
37
+ expect(subject[:audience_size][:min]).to eq(41133600)
38
+ expect(subject[:audience_size][:max]).to eq(50274400)
39
+ end
42
40
 
43
41
  end