twitter-ads 5.2.0 → 6.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/twitter-ads.rb +4 -1
  3. data/lib/twitter-ads/account.rb +9 -8
  4. data/lib/twitter-ads/campaign/campaign.rb +1 -2
  5. data/lib/twitter-ads/campaign/funding_instrument.rb +1 -2
  6. data/lib/twitter-ads/campaign/line_item.rb +2 -3
  7. data/lib/twitter-ads/campaign/organic_tweet.rb +1 -3
  8. data/lib/twitter-ads/campaign/targeting_criteria.rb +0 -1
  9. data/lib/twitter-ads/campaign/tweet.rb +4 -49
  10. data/lib/twitter-ads/client.rb +2 -2
  11. data/lib/twitter-ads/creative/account_media.rb +4 -6
  12. data/lib/twitter-ads/creative/draft_tweet.rb +40 -0
  13. data/lib/twitter-ads/creative/image_app_download_card.rb +2 -2
  14. data/lib/twitter-ads/creative/image_conversation_card.rb +3 -2
  15. data/lib/twitter-ads/creative/media_creative.rb +1 -2
  16. data/lib/twitter-ads/creative/media_library.rb +2 -4
  17. data/lib/twitter-ads/creative/promoted_account.rb +1 -2
  18. data/lib/twitter-ads/creative/promoted_tweet.rb +1 -2
  19. data/lib/twitter-ads/creative/scheduled_tweet.rb +1 -12
  20. data/lib/twitter-ads/creative/tweets.rb +52 -0
  21. data/lib/twitter-ads/creative/video_app_download_card.rb +4 -6
  22. data/lib/twitter-ads/creative/video_conversation_card.rb +6 -6
  23. data/lib/twitter-ads/creative/video_website_card.rb +3 -5
  24. data/lib/twitter-ads/creative/website_card.rb +2 -2
  25. data/lib/twitter-ads/cursor.rb +6 -0
  26. data/lib/twitter-ads/enum.rb +10 -5
  27. data/lib/twitter-ads/error.rb +5 -15
  28. data/lib/twitter-ads/http/request.rb +30 -1
  29. data/lib/twitter-ads/http/response.rb +1 -13
  30. data/lib/twitter-ads/resources/analytics.rb +99 -47
  31. data/lib/twitter-ads/resources/dsl.rb +8 -1
  32. data/lib/twitter-ads/restapi.rb +29 -0
  33. data/lib/twitter-ads/settings/tax.rb +13 -1
  34. data/lib/twitter-ads/targeting_criteria/conversation.rb +23 -0
  35. data/lib/twitter-ads/utils.rb +23 -0
  36. data/lib/twitter-ads/version.rb +1 -1
  37. data/spec/fixtures/tweet_previews.json +23 -0
  38. data/spec/twitter-ads/campaign/targeting_criteria_spec.rb +0 -1
  39. data/spec/twitter-ads/campaign/tweet_spec.rb +0 -59
  40. data/spec/twitter-ads/client_spec.rb +17 -1
  41. data/spec/twitter-ads/creative/tweet_previews_spec.rb +41 -0
  42. data/spec/twitter-ads/rate_limit_spec.rb +247 -0
  43. data/spec/twitter-ads/retry_count_spec.rb +61 -0
  44. metadata +14 -17
  45. data/lib/twitter-ads/audiences/audience_intelligence.rb +0 -68
  46. data/spec/fixtures/tweet_preview.json +0 -24
  47. data/spec/twitter-ads/creative/account_media_spec.rb +0 -32
  48. data/spec/twitter-ads/creative/image_app_download_card_spec.rb +0 -43
  49. data/spec/twitter-ads/creative/image_conversation_card_spec.rb +0 -40
  50. data/spec/twitter-ads/creative/video_app_download_card_spec.rb +0 -42
  51. data/spec/twitter-ads/creative/video_conversation_card_spec.rb +0 -51
  52. data/spec/twitter-ads/creative/website_card_spec.rb +0 -42
@@ -18,15 +18,13 @@ module TwitterAds
18
18
  property :deleted, type: :bool, read_only: true
19
19
  property :id, read_only: true
20
20
  property :updated_at, type: :time, read_only: true
21
- property :video_content_id, read_only: true
22
- property :video_hls_url, read_only: true
23
21
  property :video_owner_id, read_only: true
24
- property :video_poster_url, read_only: true
25
- property :video_url, read_only: true
22
+ property :poster_media_url, read_only: true
23
+ property :media_url, read_only: true
26
24
 
27
25
  property :country_code
28
26
  property :app_cta
29
- property :image_media_id
27
+ property :poster_media_key
30
28
  property :ipad_app_id
31
29
  property :ipad_deep_link
32
30
  property :iphone_app_id
@@ -34,7 +32,7 @@ module TwitterAds
34
32
  property :googleplay_app_id
35
33
  property :googleplay_deep_link
36
34
  property :name
37
- property :video_id
35
+ property :media_key
38
36
 
39
37
  RESOURCE_COLLECTION = "/#{TwitterAds::API_VERSION}/" \
40
38
  'accounts/%{account_id}/cards/video_app_download' # @api private
@@ -18,14 +18,14 @@ module TwitterAds
18
18
  property :deleted, type: :bool, read_only: true
19
19
  property :id, read_only: true
20
20
  property :updated_at, type: :time, read_only: true
21
- property :video_url, read_only: true
22
- property :video_poster_url, read_only: true
21
+ property :media_url, read_only: true
22
+ property :poster_media_url, read_only: true
23
23
 
24
- property :cover_image_id
25
- property :cover_video_id
24
+ property :unlocked_image_media_key
25
+ property :unlocked_video_media_key
26
26
  property :fourth_cta
27
27
  property :fourth_cta_tweet
28
- property :image_media_id
28
+ property :poster_media_key
29
29
  property :first_cta
30
30
  property :first_cta_tweet
31
31
  property :name
@@ -36,7 +36,7 @@ module TwitterAds
36
36
  property :third_cta
37
37
  property :third_cta_tweet
38
38
  property :title
39
- property :video_id
39
+ property :media_key
40
40
 
41
41
  RESOURCE_COLLECTION = "/#{TwitterAds::API_VERSION}/" \
42
42
  'accounts/%{account_id}/cards/video_conversation' # @api private
@@ -19,21 +19,19 @@ module TwitterAds
19
19
  property :deleted, type: :bool, read_only: true
20
20
  property :id, read_only: true
21
21
  property :updated_at, type: :time, read_only: true
22
- property :video_content_id, read_only: true
23
22
  property :video_height, read_only: true
24
- property :video_hls_url, read_only: true
25
23
  property :video_owner_id, read_only: true
26
24
  property :video_poster_height, read_only: true
27
- property :video_poster_url, read_only: true
25
+ property :poster_media_url, read_only: true
28
26
  property :video_poster_width, read_only: true
29
- property :video_url, read_only: true
27
+ property :media_url, read_only: true
30
28
  property :video_width, read_only: true
31
29
  property :website_display_url, read_only: true
32
30
  property :website_dest_url, read_only: true
33
31
 
34
32
  property :name
35
33
  property :title
36
- property :video_id
34
+ property :media_key
37
35
  property :website_url
38
36
 
39
37
  RESOURCE_COLLECTION = "/#{TwitterAds::API_VERSION}/accounts/%{account_id}/cards/video_website"
@@ -17,14 +17,14 @@ module TwitterAds
17
17
  property :created_at, type: :time, read_only: true
18
18
  property :deleted, type: :bool, read_only: true
19
19
  property :id, read_only: true
20
- property :image, read_only: true
20
+ property :media_url, read_only: true
21
21
  property :image_display_height, read_only: true
22
22
  property :image_display_width, read_only: true
23
23
  property :updated_at, type: :time, read_only: true
24
24
  property :website_dest_url, read_only: true
25
25
  property :website_display_url, read_only: true
26
26
 
27
- property :image_media_id
27
+ property :media_key
28
28
  property :name
29
29
  property :website_title
30
30
  property :website_url
@@ -112,6 +112,12 @@ module TwitterAds
112
112
  def from_response(response)
113
113
  @next_cursor = response.body[:next_cursor]
114
114
  @total_count = response.body[:total_count].to_i if response.body.key?(:total_count)
115
+
116
+ TwitterAds::Utils.extract_response_headers(response.headers).each { |key, value|
117
+ singleton_class.class_eval { attr_accessor key }
118
+ instance_variable_set("@#{key}", value)
119
+ }
120
+
115
121
  response.body.fetch(:data, []).each do |object|
116
122
  @collection << if @klass&.method_defined?(:from_response)
117
123
  @klass.new(
@@ -106,6 +106,7 @@ module TwitterAds
106
106
  FUNDING_INSTRUMENT = 'FUNDING_INSTRUMENT'
107
107
  CAMPAIGN = 'CAMPAIGN'
108
108
  LINE_ITEM = 'LINE_ITEM'
109
+ PROMOTED_ACCOUNT = 'PROMOTED_ACCOUNT'
109
110
  PROMOTED_TWEET = 'PROMOTED_TWEET'
110
111
  ORGANIC_TWEET = 'ORGANIC_TWEET'
111
112
  MEDIA_CREATIVE = 'MEDIA_CREATIVE'
@@ -143,7 +144,10 @@ module TwitterAds
143
144
  end
144
145
 
145
146
  module Optimizations
147
+ APP_CLICKS = 'APP_CLICKS'
148
+ APP_INSTALLS = 'APP_INSTALLS'
146
149
  DEFAULT = 'DEFAULT'
150
+ ENGAGEMENTS = 'ENGAGEMENTS'
147
151
  WEBSITE_CONVERSIONS = 'WEBSITE_CONVERSIONS'
148
152
  end
149
153
 
@@ -218,11 +222,6 @@ module TwitterAds
218
222
  HASHTAG = 'HASHTAG'
219
223
  end
220
224
 
221
- module AudienceDefinition
222
- TARGETING_CRITERIA = 'TARGETING_CRITERIA'
223
- KEYWORD_AUDIENCE = 'KEYWORD_AUDIENCE'
224
- end
225
-
226
225
  module LookalikeExpansion
227
226
  DEFINED = 'DEFINED'
228
227
  EXPANDED = 'EXPANDED'
@@ -233,5 +232,11 @@ module TwitterAds
233
232
  PUBLISHED = 'PUBLISHED'
234
233
  SCHEDULED = 'SCHEDULED'
235
234
  end
235
+
236
+ module TimelineType
237
+ ALL = 'ALL'
238
+ NULLCAST = 'NULLCAST'
239
+ ORGANIC = 'ORGANIC'
240
+ end
236
241
  end
237
242
  end
@@ -59,18 +59,7 @@ module TwitterAds
59
59
 
60
60
  # Server Errors (5XX)
61
61
  class ServerError < Error; end
62
-
63
- class ServiceUnavailable < ServerError
64
- attr_reader :retry_after
65
-
66
- def initialize(object)
67
- super object
68
- if object.headers['retry-after']
69
- @retry_after = object.headers['retry-after']
70
- end
71
- self
72
- end
73
- end
62
+ class ServiceUnavailable < ServerError; end
74
63
 
75
64
  # Client Errors (4XX)
76
65
  class ClientError < Error; end
@@ -80,12 +69,13 @@ module TwitterAds
80
69
  class BadRequest < ClientError; end
81
70
 
82
71
  class RateLimit < ClientError
83
- attr_reader :reset_at, :retry_after
72
+ attr_reader :reset_at
84
73
 
85
74
  def initialize(object)
86
75
  super object
87
- @retry_after = object.headers['retry-after']
88
- @reset_at = object.headers['rate_limit_reset']
76
+ header = object.headers.fetch('x-account-rate-limit-reset', nil) ||
77
+ object.headers.fetch('x-rate-limit-reset', nil)
78
+ @reset_at = header.first.to_i
89
79
  self
90
80
  end
91
81
  end
@@ -71,8 +71,37 @@ module TwitterAds
71
71
  token = OAuth::AccessToken.new(consumer, @client.access_token, @client.access_token_secret)
72
72
  request.oauth!(consumer.http, consumer, token)
73
73
 
74
+ handle_rate_limit = @client.options.fetch(:handle_rate_limit, false)
75
+ retry_max = @client.options.fetch(:retry_max, 0)
76
+ retry_delay = @client.options.fetch(:retry_delay, 1500)
77
+ retry_on_status = @client.options.fetch(:retry_on_status, [500, 503])
78
+ retry_count = 0
79
+ retry_after = nil
80
+
74
81
  write_log(request) if @client.options[:trace]
75
- response = consumer.http.request(request)
82
+ while retry_count <= retry_max
83
+ response = consumer.http.request(request)
84
+ status_code = response.code.to_i
85
+ break if status_code >= 200 && status_code < 300
86
+
87
+ if handle_rate_limit && retry_after.nil?
88
+ rate_limit_reset = response.fetch('x-account-rate-limit-reset', nil) ||
89
+ response.fetch('x-rate-limit-reset', nil)
90
+ if status_code == 429
91
+ retry_after = rate_limit_reset.to_i - Time.now.to_i
92
+ @client.logger.warn('Request reached Rate Limit: resume in %d seconds' % retry_after)
93
+ sleep(retry_after + 5)
94
+ next
95
+ end
96
+ end
97
+
98
+ if retry_max.positive?
99
+ break unless retry_on_status.include?(status_code)
100
+ sleep(retry_delay / 1000)
101
+ end
102
+
103
+ retry_count += 1
104
+ end
76
105
  write_log(response) if @client.options[:trace]
77
106
 
78
107
  Response.new(response.code, response.each {}, response.body)
@@ -9,9 +9,7 @@ module TwitterAds
9
9
  attr_reader :code,
10
10
  :headers,
11
11
  :raw_body,
12
- :body,
13
- :rate_limit_remaining,
14
- :rate_limit_reset
12
+ :body
15
13
 
16
14
  # Creates a new Response object instance.
17
15
  #
@@ -37,16 +35,6 @@ module TwitterAds
37
35
  @body = raw_body
38
36
  end
39
37
 
40
- if headers.key?('x-rate-limit-reset')
41
- @rate_limit = headers['x-rate-limit-limit'].first
42
- @rate_limit_remaining = headers['x-rate-limit-remaining'].first
43
- @rate_limit_reset = headers['x-rate-limit-reset'].first.to_i
44
- elsif headers.key?('x-cost-rate-limit-reset')
45
- @rate_limit = headers['x-cost-rate-limit-limit'].first
46
- @rate_limit_remaining = headers['x-cost-rate-limit-remaining'].first
47
- @rate_limit_reset = Time.at(headers['x-cost-rate-limit-reset'].first.to_i)
48
- end
49
-
50
38
  self
51
39
  end
52
40
 
@@ -5,15 +5,35 @@ require 'zlib'
5
5
  require 'open-uri'
6
6
 
7
7
  module TwitterAds
8
- module Analytics
8
+ class Analytics
9
9
 
10
+ include TwitterAds::DSL
11
+ include TwitterAds::Resource
10
12
  include TwitterAds::Enum
11
13
 
14
+ attr_reader :account
15
+
16
+ property :id, read_only: true
17
+ property :id_str, read_only: true
18
+ property :status, read_only: true
19
+ property :url, read_only: true
20
+ property :created_at, type: :time, read_only: true
21
+ property :expires_at, type: :time, read_only: true
22
+ property :updated_at, type: :time, read_only: true
23
+ property :start_time, type: :time, read_only: true
24
+ property :end_time, type: :time, read_only: true
25
+
26
+ property :entity, read_only: true
27
+ property :entity_ids, read_only: true
28
+ property :placement, read_only: true
29
+ property :granularity, read_only: true
30
+ property :metric_groups, read_only: true
31
+
12
32
  ANALYTICS_MAP = {
13
33
  'TwitterAds::Campaign' => Entity::CAMPAIGN,
14
34
  'TwitterAds::LineItem' => Entity::LINE_ITEM,
15
35
  'TwitterAds::OrganicTweet' => Entity::ORGANIC_TWEET,
16
- 'TwitterAds::Creative::PromotedAccount' => Entity::ACCOUNT,
36
+ 'TwitterAds::Creative::PromotedAccount' => Entity::PROMOTED_ACCOUNT,
17
37
  'TwitterAds::Creative::PromotedTweet' => Entity::PROMOTED_TWEET,
18
38
  'TwitterAds::Creative::MediaCreative' => Entity::MEDIA_CREATIVE
19
39
  }.freeze
@@ -25,42 +45,38 @@ module TwitterAds
25
45
  RESOURCE_ACTIVE_ENTITIES = "/#{TwitterAds::API_VERSION}/" +
26
46
  'stats/accounts/%{account_id}/active_entities' # @api private
27
47
 
28
- def self.included(klass)
29
- klass.send :include, InstanceMethods
30
- klass.extend ClassMethods
48
+ def initialize(account)
49
+ @account = account
50
+ self
31
51
  end
32
52
 
33
- module InstanceMethods
34
-
35
- # Pulls a list of metrics for the current object instance.
36
- #
37
- # @example
38
- # metric_groups = [:promoted_tweet_timeline_clicks, :promoted_tweet_search_clicks]
39
- # object.stats(metrics)
40
- #
41
- # @param metric_groups [Array] A collection of metric groups to fetch.
42
- # @param opts [Hash] An optional Hash of extended options.
43
- # @option opts [Time] :start_time The starting time to use (default: 7 days ago).
44
- # @option opts [Time] :end_time The end time to use (default: now).
45
- # @option opts [Symbol] :granularity The granularity to use (default: :hour).
46
- #
47
- # @return [Array] The collection of stats requested.
48
- #
49
- # @see https://dev.twitter.com/ads/analytics/metrics-and-segmentation
50
- # @since 1.0.0
51
- def stats(metric_groups, opts = {})
52
- self.class.stats(account, [id], metric_groups, opts)
53
- end
54
-
53
+ # Pulls a list of metrics for the current object instance.
54
+ #
55
+ # @example
56
+ # metric_groups = [MetricGroup::MOBILE_CONVERSION, MetricGroup::ENGAGEMENT]
57
+ # object.stats(metrics)
58
+ #
59
+ # @param metric_groups [Array] A collection of metric groups to fetch.
60
+ # @param opts [Hash] An optional Hash of extended options.
61
+ # @option opts [Time] :start_time The starting time to use (default: 7 days ago).
62
+ # @option opts [Time] :end_time The end time to use (default: now).
63
+ # @option opts [Symbol] :granularity The granularity to use (default: :hour).
64
+ #
65
+ # @return [Array] The collection of stats requested.
66
+ #
67
+ # @see https://dev.twitter.com/ads/analytics/metrics-and-segmentation
68
+ # @since 1.0.0
69
+ def stats(metric_groups, opts = {})
70
+ self.class.stats(account, [id], metric_groups, opts)
55
71
  end
56
72
 
57
- module ClassMethods
73
+ class << self
58
74
 
59
75
  # Pulls a list of metrics for a specified set of object IDs.
60
76
  #
61
77
  # @example
62
78
  # ids = ['7o4em', 'oc9ce', '1c5lji']
63
- # metric_groups = [MetricGroups.MOBILE_CONVERSION, MetricGroups.ENGAGEMENT]
79
+ # metric_groups = [MetricGroup::MOBILE_CONVERSION, MetricGroup::ENGAGEMENT]
64
80
  # object.stats(account, ids, metric_groups)
65
81
  #
66
82
  # @param account [Account] The Account object instance.
@@ -70,7 +86,7 @@ module TwitterAds
70
86
  # @option opts [Time] :start_time The starting time to use (default: 7 days ago).
71
87
  # @option opts [Time] :end_time The end time to use (default: now).
72
88
  # @option opts [Symbol] :granularity The granularity to use (default: :hour).
73
- # @option opts [Symbol] :placement The placement of entity (default: ALL_ON_TWITTER).
89
+ # @option opts [String] :placement The placement of entity (default: ALL_ON_TWITTER).
74
90
  #
75
91
  # @return [Array] The collection of stats requested.
76
92
  #
@@ -108,7 +124,7 @@ module TwitterAds
108
124
  #
109
125
  # @example
110
126
  # ids = ['7o4em', 'oc9ce', '1c5lji']
111
- # metric_groups = [MetricGroups.MOBILE_CONVERSION, MetricGroups.ENGAGEMENT]
127
+ # metric_groups = [MetricGroup::MOBILE_CONVERSION, MetricGroup::ENGAGEMENT]
112
128
  # object.create_async_job(account, ids, metric_groups)
113
129
  #
114
130
  # @param account [Account] The Account object instance.
@@ -118,16 +134,17 @@ module TwitterAds
118
134
  # @option opts [Time] :start_time The starting time to use (default: 7 days ago).
119
135
  # @option opts [Time] :end_time The end time to use (default: now).
120
136
  # @option opts [Symbol] :granularity The granularity to use (default: :hour).
121
- # @option opts [Symbol] :placement The placement of entity (default: ALL_ON_TWITTER).
122
- # @option opts [Symbol] :segmentation_type The segmentation type to use (default: none).
137
+ # @option opts [String] :placement The placement of entity (default: ALL_ON_TWITTER).
138
+ # @option opts [String] :segmentation_type The segmentation type to use (default: none).
123
139
  #
124
140
  # @return The response of creating job
125
141
  #
126
142
  # @see https://dev.twitter.com/ads/analytics/metrics-and-segmentation
127
- # @sync 1.0.0
143
+ # @since 1.0.0
128
144
 
129
145
  def create_async_job(account, ids, metric_groups, opts = {})
130
146
  # set default metric values
147
+ entity = opts.fetch(:entity, name)
131
148
  end_time = opts.fetch(:end_time, (Time.now - Time.now.sec - (60 * Time.now.min)))
132
149
  start_time = opts.fetch(:start_time, end_time - 604_800) # 7 days ago
133
150
  granularity = opts.fetch(:granularity, :hour)
@@ -143,7 +160,7 @@ module TwitterAds
143
160
  start_time: TwitterAds::Utils.to_time(start_time, granularity, start_utc_offset),
144
161
  end_time: TwitterAds::Utils.to_time(end_time, granularity, end_utc_offset),
145
162
  granularity: granularity.to_s.upcase,
146
- entity: ANALYTICS_MAP[name],
163
+ entity: ANALYTICS_MAP[entity],
147
164
  placement: placement,
148
165
  country: country,
149
166
  platform: platform
@@ -153,19 +170,18 @@ module TwitterAds
153
170
  params['entity_ids'] = ids.join(',')
154
171
 
155
172
  resource = self::RESOURCE_ASYNC_STATS % { account_id: account.id }
156
- puts 'my resource is ' + resource
157
173
  response = Request.new(account.client, :post, resource, params: params).perform
158
- response.body[:data]
174
+ TwitterAds::Analytics.new(account).from_response(response.body[:data], response.headers)
159
175
  end
160
176
 
161
177
  # Check async job status.
162
178
  # GET /#{TwitterAds::API_VERSION}/stats/jobs/accounts/:account_id
163
179
  #
164
180
  # @example
165
- # TwitterAds::LineItem.check_async_job_status(account, job_id: '1357343438724431305')
181
+ # TwitterAds::LineItem.check_async_job_status(account, job_ids: ['1357343438724431305'])
166
182
  #
167
183
  # @param account [Account] The Account object instance.
168
- # @option opts [String] :job_id The starting time to use (default: 7 days ago).
184
+ # @option opts [Array] :job_ids A collection of job IDs to fetch.
169
185
  #
170
186
  # @return A cursor of job statuses
171
187
 
@@ -173,18 +189,18 @@ module TwitterAds
173
189
  # set default values
174
190
  job_ids = opts.fetch(:job_ids, nil)
175
191
  params = {}
176
- params[:job_ids] = Array.wrap(job_ids).join(',') if job_ids
192
+ params[:job_ids] = job_ids.join(',') if job_ids
177
193
 
178
194
  resource = self::RESOURCE_ASYNC_STATS % { account_id: account.id }
179
195
  request = Request.new(account.client, :get, resource, params: params)
180
- Cursor.new(nil, request, init_with: [account])
196
+ Cursor.new(TwitterAds::Analytics, request, init_with: [account])
181
197
  end
182
198
 
183
199
  # Fetch async job data for a completed job.
184
200
  # Raises HTTP 404 exception, otherwise retries up to 5 times with exponential backoff.
185
201
  #
186
202
  # @example
187
- # response_data = TwitterAds::LineItem.fetch_async_job_data(account, file_url)
203
+ # response_data = TwitterAds::LineItem.fetch_async_job_data(account, data_url)
188
204
  #
189
205
  # @param data_url [String] The URL from the successful completion of an async job.
190
206
  #
@@ -209,21 +225,57 @@ module TwitterAds
209
225
  end
210
226
  end
211
227
 
228
+ # Retrieve details about which entities' analytics metrics
229
+ # have changed in a given time period.
230
+ #
231
+ # @example
232
+ # time = Time.now
233
+ # utc_offset = '+09:00'
234
+ # start_time = time - (60 * 60 * 24) # -1 day
235
+ # end_time = time
236
+ # active_entities = TwitterAds::LineItem.active_entities(
237
+ # account,
238
+ # line_item_ids: %w(exrfs),
239
+ # start_time: start_time,
240
+ # end_time: end_time,
241
+ # utc_offset: utc_offset,
242
+ # granularity: :day)
243
+ #
244
+ # @param account [Account] The Account object instance.
245
+ # @param entity [String] The entity type to retrieve data for.
246
+ # @param start_time [Time] Scopes the retrieved data to the specified start time.
247
+ # @param end_time [Time] Scopes the retrieved data to the specified end time.
248
+ # @option opts [Array] :campaign_ids A collection of IDs to be fetched.
249
+ # @option opts [Array] :funding_instrument_ids A collection of IDs to be fetched.
250
+ # @option opts [Array] :line_item_ids A collection of IDs to be fetched.
251
+ #
252
+ # @return A list of entity details.
253
+ #
254
+ # @see https://developer.twitter.com/en/docs/ads/analytics/api-reference/active-entities
255
+
212
256
  def active_entities(account, start_time:, end_time:, **opts)
213
- entity_type = name
257
+ entity = opts.fetch(:entity, name)
214
258
  granularity = opts.fetch(:granularity, nil)
215
259
  start_utc_offset = opts[:start_utc_offset] || opts[:utc_offset]
216
260
  end_utc_offset = opts[:end_utc_offset] || opts[:utc_offset]
217
261
 
218
- if entity_type == 'OrganicTweet'
219
- raise "'OrganicTweet' not support with 'active_entities'"
262
+ if entity == 'OrganicTweet'
263
+ raise "'OrganicTweet' is not supported with 'active_entities'"
220
264
  end
221
265
 
222
266
  params = {
223
- entity: ANALYTICS_MAP[entity_type],
267
+ entity: ANALYTICS_MAP[entity],
224
268
  start_time: TwitterAds::Utils.to_time(start_time, granularity, start_utc_offset),
225
269
  end_time: TwitterAds::Utils.to_time(end_time, granularity, end_utc_offset)
226
- }.merge!(opts)
270
+ }
271
+
272
+ opts.each { |k, v|
273
+ params[k] = if v.instance_of?(Array)
274
+ v.join(',')
275
+ else
276
+ v
277
+ end
278
+ }
227
279
 
228
280
  resource = self::RESOURCE_ACTIVE_ENTITIES % { account_id: account.id }
229
281
  response = Request.new(account.client, :get, resource, params: params).perform