twitter-ads 5.0.0 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +1 -1
- data/README.md +3 -4
- data/Rakefile +1 -2
- data/lib/twitter-ads.rb +10 -11
- data/lib/twitter-ads/account.rb +12 -12
- data/lib/twitter-ads/audiences/audience_intelligence.rb +3 -3
- data/lib/twitter-ads/audiences/tailored_audience.rb +12 -156
- data/lib/twitter-ads/campaign/app_list.rb +5 -5
- data/lib/twitter-ads/campaign/campaign.rb +7 -11
- data/lib/twitter-ads/campaign/funding_instrument.rb +6 -5
- data/lib/twitter-ads/campaign/iab_category.rb +2 -2
- data/lib/twitter-ads/campaign/line_item.rb +18 -21
- data/lib/twitter-ads/campaign/organic_tweet.rb +1 -5
- data/lib/twitter-ads/campaign/promotable_user.rb +5 -5
- data/lib/twitter-ads/campaign/targeting_criteria.rb +7 -7
- data/lib/twitter-ads/campaign/tweet.rb +7 -11
- data/lib/twitter-ads/client.rb +3 -4
- data/lib/twitter-ads/creative/account_media.rb +5 -5
- data/lib/twitter-ads/creative/cards_fetch.rb +8 -6
- data/lib/twitter-ads/creative/image_app_download_card.rb +5 -5
- data/lib/twitter-ads/creative/image_conversation_card.rb +5 -5
- data/lib/twitter-ads/creative/media_creative.rb +6 -5
- data/lib/twitter-ads/creative/media_library.rb +5 -5
- data/lib/twitter-ads/creative/poll_cards.rb +5 -5
- data/lib/twitter-ads/creative/promoted_account.rb +5 -9
- data/lib/twitter-ads/creative/promoted_tweet.rb +5 -9
- data/lib/twitter-ads/creative/scheduled_tweet.rb +7 -7
- data/lib/twitter-ads/creative/tweet_previews.rb +36 -0
- data/lib/twitter-ads/creative/video_app_download_card.rb +5 -5
- data/lib/twitter-ads/creative/video_conversation_card.rb +5 -5
- data/lib/twitter-ads/creative/video_website_card.rb +4 -4
- data/lib/twitter-ads/creative/website_card.rb +3 -3
- data/lib/twitter-ads/cursor.rb +2 -2
- data/lib/twitter-ads/enum.rb +128 -122
- data/lib/twitter-ads/error.rb +2 -2
- data/lib/twitter-ads/http/request.rb +6 -5
- data/lib/twitter-ads/http/response.rb +1 -1
- data/lib/twitter-ads/legacy.rb +1 -1
- data/lib/twitter-ads/measurement/app_event_tag.rb +3 -3
- data/lib/twitter-ads/measurement/web_event_tag.rb +3 -3
- data/lib/twitter-ads/resources/analytics.rb +38 -8
- data/lib/twitter-ads/resources/batch.rb +4 -4
- data/lib/twitter-ads/resources/dsl.rb +1 -1
- data/lib/twitter-ads/resources/persistence.rb +1 -1
- data/lib/twitter-ads/resources/resource.rb +2 -2
- data/lib/twitter-ads/settings/tax.rb +3 -3
- data/lib/twitter-ads/settings/user.rb +3 -3
- data/lib/twitter-ads/targeting/reach_estimate.rb +1 -1
- data/lib/twitter-ads/targeting_criteria/app_store_category.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/behavior.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/behavior_taxonomy.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/device.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/event.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/interest.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/language.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/location.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/network_operator.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/platform.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/platform_version.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/tv_market.rb +3 -3
- data/lib/twitter-ads/targeting_criteria/tv_show.rb +3 -3
- data/lib/twitter-ads/utils.rb +2 -2
- data/lib/twitter-ads/version.rb +2 -2
- data/spec/quality_spec.rb +2 -2
- data/spec/shared/properties.rb +1 -1
- data/spec/spec_helper.rb +9 -8
- data/spec/support/helpers.rb +1 -1
- data/spec/twitter-ads/account_spec.rb +1 -1
- data/spec/twitter-ads/audiences/tailored_audience_spec.rb +1 -1
- data/spec/twitter-ads/campaign/app_list_spec.rb +1 -1
- data/spec/twitter-ads/campaign/line_item_spec.rb +1 -1
- data/spec/twitter-ads/campaign/reach_estimate_spec.rb +1 -1
- data/spec/twitter-ads/campaign/targeting_criteria_spec.rb +1 -1
- data/spec/twitter-ads/campaign/tweet_spec.rb +1 -1
- data/spec/twitter-ads/client_spec.rb +1 -1
- data/spec/twitter-ads/creative/account_media_spec.rb +1 -1
- data/spec/twitter-ads/creative/image_app_download_card_spec.rb +1 -1
- data/spec/twitter-ads/creative/image_conversation_card_spec.rb +1 -1
- data/spec/twitter-ads/creative/media_creative_spec.rb +1 -1
- data/spec/twitter-ads/creative/promoted_account_spec.rb +1 -1
- data/spec/twitter-ads/creative/promoted_tweet_spec.rb +1 -1
- data/spec/twitter-ads/creative/video_app_download_card_spec.rb +1 -1
- data/spec/twitter-ads/creative/video_conversation_card_spec.rb +1 -1
- data/spec/twitter-ads/creative/website_card_spec.rb +1 -1
- data/spec/twitter-ads/cursor_spec.rb +1 -1
- data/spec/twitter-ads/placements_spec.rb +1 -1
- data/spec/twitter-ads/utils_spec.rb +1 -1
- data/twitter-ads.gemspec +10 -6
- metadata +10 -25
- data/lib/twitter-ads/http/ton_upload.rb +0 -156
- data/spec/twitter-ads/http/ton_upload_spec.rb +0 -96
@@ -1,156 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# Copyright (C) 2015 Twitter, Inc.
|
3
|
-
|
4
|
-
module TwitterAds
|
5
|
-
|
6
|
-
# Specialized request class for TON API uploads.
|
7
|
-
class TONUpload
|
8
|
-
|
9
|
-
DEFAULT_DOMAIN = 'https://ton.twitter.com'.freeze # @api private
|
10
|
-
DEFAULT_RESOURCE = '/1.1/ton/bucket/'.freeze # @api private
|
11
|
-
DEFAULT_BUCKET = 'ta_partner'.freeze # @api private
|
12
|
-
DEFAULT_EXPIRE = (Time.now + 10 * 24 * 60 * 60).httpdate # @api private
|
13
|
-
DEFAULT_CHUNK_SIZE = 64 # @api private
|
14
|
-
SINGLE_UPLOAD_MAX = 1024 * 1024 * DEFAULT_CHUNK_SIZE # @api private
|
15
|
-
# target response time (ref. https://github.com/twitterdev/twitter-ruby-ads-sdk/pull/161)
|
16
|
-
RESPONSE_TIME_MAX = 5000 # @api private
|
17
|
-
|
18
|
-
private_constant :DEFAULT_DOMAIN,
|
19
|
-
:DEFAULT_RESOURCE,
|
20
|
-
:DEFAULT_BUCKET,
|
21
|
-
:DEFAULT_EXPIRE,
|
22
|
-
:DEFAULT_CHUNK_SIZE,
|
23
|
-
:SINGLE_UPLOAD_MAX,
|
24
|
-
:RESPONSE_TIME_MAX
|
25
|
-
|
26
|
-
# Creates a new TONUpload object instance.
|
27
|
-
#
|
28
|
-
# @example
|
29
|
-
# request = TONUpload.new(client, '/path/to/file')
|
30
|
-
#
|
31
|
-
# @param client [Client] The Client object instance.
|
32
|
-
# @param file_path [String] The path to the file to be uploaded.
|
33
|
-
#
|
34
|
-
# @since 0.3.0
|
35
|
-
#
|
36
|
-
# @return [TONUpload] The TONUpload request instance.
|
37
|
-
def initialize(client, file_path, opts = {})
|
38
|
-
@file_path = File.expand_path(file_path)
|
39
|
-
unless File.exist?(file_path)
|
40
|
-
raise ArgumentError.new("Error! The specified file does not exist. (#{file_path})")
|
41
|
-
end
|
42
|
-
|
43
|
-
@file_size = File.size(@file_path)
|
44
|
-
|
45
|
-
@client = client
|
46
|
-
@bucket = opts.delete(:bucket) || DEFAULT_BUCKET
|
47
|
-
@options = opts
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
# Executes the current TONUpload object.
|
52
|
-
#
|
53
|
-
# @example
|
54
|
-
# request = TONUpload.new(client, '/path/to/file')
|
55
|
-
# request.perform
|
56
|
-
#
|
57
|
-
# @since 0.3.0
|
58
|
-
#
|
59
|
-
# @return [String] The upload location provided by the TON API.
|
60
|
-
def perform
|
61
|
-
if @file_size < SINGLE_UPLOAD_MAX
|
62
|
-
resource = "#{DEFAULT_RESOURCE}#{@bucket}"
|
63
|
-
response = upload(resource, File.read(@file_path))
|
64
|
-
response.headers['location'][0]
|
65
|
-
else
|
66
|
-
response = init_chunked_upload
|
67
|
-
bytes_per_chunk_size = response.headers['x-ton-min-chunk-size'][0].to_i
|
68
|
-
location = response.headers['location'][0]
|
69
|
-
|
70
|
-
bytes_read = 0
|
71
|
-
chunk_bytes = bytes_per_chunk_size * DEFAULT_CHUNK_SIZE
|
72
|
-
File.open(@file_path) do |file|
|
73
|
-
while bytes = file.read(chunk_bytes)
|
74
|
-
bytes_start = bytes_read
|
75
|
-
bytes_read += bytes.size
|
76
|
-
upload_chunk(location, bytes, bytes_start, bytes_read) do |res|
|
77
|
-
# Determines the chunk bytes based on response times
|
78
|
-
response_time = res.headers['x-response-time'][0].to_f
|
79
|
-
response_based_chunk_size =
|
80
|
-
(DEFAULT_CHUNK_SIZE * (RESPONSE_TIME_MAX / response_time)).to_i
|
81
|
-
next_chunk_size = [DEFAULT_CHUNK_SIZE, [1, response_based_chunk_size].max].min
|
82
|
-
chunk_bytes = bytes_per_chunk_size * next_chunk_size
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
location.split('?')[0]
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# Returns an inspection string for the current object instance.
|
92
|
-
#
|
93
|
-
# @since 0.3.0
|
94
|
-
#
|
95
|
-
# @return [String] The object instance detail.
|
96
|
-
def inspect
|
97
|
-
"#<#{self.class.name}:0x#{object_id} bucket=\"#{@bucket}\" file=\"#{@file_path}\">"
|
98
|
-
end
|
99
|
-
|
100
|
-
private
|
101
|
-
|
102
|
-
# performs a single chunk upload
|
103
|
-
def upload(resource, bytes)
|
104
|
-
headers = {
|
105
|
-
'x-ton-expires' => DEFAULT_EXPIRE,
|
106
|
-
'content-length' => @file_size,
|
107
|
-
'content-type' => content_type
|
108
|
-
}
|
109
|
-
TwitterAds::Request.new(
|
110
|
-
@client, :post, resource, domain: DEFAULT_DOMAIN, headers: headers, body: bytes).perform
|
111
|
-
end
|
112
|
-
|
113
|
-
# initialization for a multi-chunk upload
|
114
|
-
def init_chunked_upload
|
115
|
-
headers = {
|
116
|
-
'x-ton-content-type' => content_type,
|
117
|
-
'x-ton-content-length' => @file_size,
|
118
|
-
'x-ton-expires' => DEFAULT_EXPIRE,
|
119
|
-
'content-length' => 0,
|
120
|
-
'content-type' => content_type
|
121
|
-
}
|
122
|
-
resource = "#{DEFAULT_RESOURCE}#{@bucket}?resumable=true"
|
123
|
-
TwitterAds::Request.new(
|
124
|
-
@client, :post, resource, domain: DEFAULT_DOMAIN, headers: headers).perform
|
125
|
-
end
|
126
|
-
|
127
|
-
# uploads a single chunk of a multi-chunk upload
|
128
|
-
def upload_chunk(resource, bytes, bytes_start, bytes_read)
|
129
|
-
headers = {
|
130
|
-
'content-type' => content_type,
|
131
|
-
'content-length' => bytes.size,
|
132
|
-
'content-range' => "bytes #{bytes_start}-#{bytes_read - 1}/#{@file_size}"
|
133
|
-
}
|
134
|
-
response = TwitterAds::Request.new(
|
135
|
-
@client, :put, resource, domain: DEFAULT_DOMAIN, headers: headers, body: bytes).perform
|
136
|
-
|
137
|
-
yield(response)
|
138
|
-
response
|
139
|
-
end
|
140
|
-
|
141
|
-
def content_type
|
142
|
-
@content_type ||= begin
|
143
|
-
extension = File.extname(@file_path).downcase
|
144
|
-
if extension == '.csv'
|
145
|
-
'text/csv'
|
146
|
-
elsif extension == '.tsv'
|
147
|
-
'text/tab-separated-values'
|
148
|
-
else
|
149
|
-
'text/plain'
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
# Copyright (C) 2015 Twitter, Inc.
|
3
|
-
|
4
|
-
require 'spec_helper'
|
5
|
-
require 'tempfile'
|
6
|
-
|
7
|
-
describe TwitterAds::TONUpload do
|
8
|
-
before(:each) do
|
9
|
-
stub_fixture(
|
10
|
-
:post, :no_content, "#{TON_API}/1.1/ton/bucket/#{bucket_name}",
|
11
|
-
headers: {
|
12
|
-
'location' => location_path
|
13
|
-
}
|
14
|
-
)
|
15
|
-
stub_fixture(
|
16
|
-
:post, :no_content, "#{TON_API}/1.1/ton/bucket/#{bucket_name}?resumable=true",
|
17
|
-
headers: {
|
18
|
-
'location' => "#{location_path}?resumable=true&resumeId=1234567",
|
19
|
-
'x-ton-min-chunk-size' => '1048576'
|
20
|
-
}
|
21
|
-
)
|
22
|
-
end
|
23
|
-
let(:bucket_name) { 'ta_partner' }
|
24
|
-
let(:location_path) { '/1.1/ton/data/ta_partner/123456789/tYfALUIK-F3ZMxj.txt' }
|
25
|
-
|
26
|
-
around(:each) do |example|
|
27
|
-
example.run
|
28
|
-
file.unlink
|
29
|
-
end
|
30
|
-
let!(:file) do
|
31
|
-
Tempfile.open(['', '.dat'], File.expand_path('../../../tmp/', __FILE__)) do |fp|
|
32
|
-
fp.tap { |f| f.write(SecureRandom.random_bytes(file_size)) }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
let(:file_size) { 100 }
|
36
|
-
|
37
|
-
let(:ton_upload) { described_class.new(client, file_path) }
|
38
|
-
let(:client) do
|
39
|
-
Client.new(
|
40
|
-
Faker::Lorem.characters(15),
|
41
|
-
Faker::Lorem.characters(40),
|
42
|
-
"123456-#{Faker::Lorem.characters(40)}",
|
43
|
-
Faker::Lorem.characters(40)
|
44
|
-
)
|
45
|
-
end
|
46
|
-
let(:file_path) { file.path }
|
47
|
-
|
48
|
-
describe '#initialize' do
|
49
|
-
|
50
|
-
subject { ton_upload }
|
51
|
-
it { is_expected.to be_an_instance_of described_class }
|
52
|
-
|
53
|
-
context 'when the file path is invalid' do
|
54
|
-
let(:file_path) { '/invalid/file/path' }
|
55
|
-
|
56
|
-
it do
|
57
|
-
expect { subject }.to raise_error ArgumentError
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
describe '#perform' do
|
63
|
-
subject { ton_upload.perform }
|
64
|
-
|
65
|
-
it 'should call TwitterAds::Request#perform exactly once' do
|
66
|
-
expect_any_instance_of(TwitterAds::Request).to receive(:perform).once.and_call_original
|
67
|
-
subject
|
68
|
-
end
|
69
|
-
|
70
|
-
it { is_expected.to eq '/1.1/ton/data/ta_partner/123456789/tYfALUIK-F3ZMxj.txt' }
|
71
|
-
|
72
|
-
context 'when the file size is larger than 64MB' do
|
73
|
-
before(:each) do
|
74
|
-
allow(ton_upload).to receive(:upload_chunk)
|
75
|
-
end
|
76
|
-
let(:file_size) { 65 * 1024 * 1024 }
|
77
|
-
|
78
|
-
it { is_expected.to eq '/1.1/ton/data/ta_partner/123456789/tYfALUIK-F3ZMxj.txt' }
|
79
|
-
it 'should call TwitterAds::Request#perform exactly once' do
|
80
|
-
expect(ton_upload).to receive(:upload_chunk).at_least(:twice)
|
81
|
-
subject
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
describe '#inspect' do
|
87
|
-
subject { ton_upload.inspect }
|
88
|
-
|
89
|
-
it do
|
90
|
-
is_expected.to include(described_class.name)
|
91
|
-
is_expected.to include(ton_upload.object_id.to_s)
|
92
|
-
is_expected.to include(bucket_name)
|
93
|
-
is_expected.to include(file_path)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|