google_analytics_feeds 0.1.1 → 0.1.2

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.
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx-18mode
5
+ - rbx-19mode
6
+ - 1.8.7
7
+ - ree
8
+ - ruby-head
9
+ notifications:
10
+ recipients:
11
+ - roland.swingler@gmail.com
@@ -0,0 +1 @@
1
+ --api public
data/Gemfile CHANGED
@@ -8,7 +8,7 @@ gem "addressable"
8
8
  # Include everything needed to run rake, tests, features, etc.
9
9
  group :development do
10
10
  gem "rspec", "~> 2"
11
- gem "rdoc", "~> 3.12"
11
+ gem "yard"
12
12
  gem "jeweler", "~> 1.8"
13
- gem "rcov", ">= 0"
13
+ gem "rcov", ">= 0", :platforms => :mri_18
14
14
  end
@@ -1,8 +1,18 @@
1
1
  = Google Analytics Feeds
2
2
 
3
- Allows access to google analytics feeds from ruby.
3
+ {<img src="https://secure.travis-ci.org/knaveofdiamonds/google_analytics_feeds.png" />}[http://travis-ci.org/knaveofdiamonds/google_analytics_feeds]
4
4
 
5
- Does not support any of the OAuth-related authentication methods.
5
+ Allows access to Google Analytics feeds from Ruby.
6
+
7
+ Does not support any of the OAuth-related authentication methods, only
8
+ session login.
9
+
10
+ It tries to be really simple, returning rows as Hashes rather than
11
+ specific objects, and provides flexibility as to how you want to make
12
+ HTTP calls via Faraday.
13
+
14
+ Tested on 1.9, 1.8 & Rubinus; depends on Ox, which has C dependencies
15
+ so not tested on JRuby.
6
16
 
7
17
  == Installation
8
18
 
@@ -26,7 +36,7 @@ Does not support any of the OAuth-related authentication methods.
26
36
  session.login("username", "password")
27
37
 
28
38
  # Fetch a report. Rows are handled with a
29
- # GoogleAnalyticsFeed::RowHandler - this may just be an anonymous
39
+ # GoogleAnalyticsFeeds::RowHandler - this may just be an anonymous
30
40
  # block as demonstrated here.
31
41
  session.fetch_report(report) do
32
42
  def row(r)
@@ -34,10 +44,24 @@ Does not support any of the OAuth-related authentication methods.
34
44
  end
35
45
  end
36
46
 
47
+ # Or you can pass a RowHandler class
48
+ class StdoutRowHandler < GoogleAnalyticsFeeds::RowHandler
49
+ def row(r)
50
+ puts r.inspect
51
+ end
52
+ end
53
+
54
+ # Either takes the class
55
+ session.fetch_report(report, StdoutRowHandler)
56
+
57
+ # Or an instance (if you needed to pass things to the constructor)
58
+ session.fetch_report(report, StdoutRowHandler.new)
59
+
60
+ For detailed documentation on how to use the Google Analytics Data Feeds, see Google's documentation: https://developers.google.com/analytics/devguides/reporting/core/v2/gdataReferenceDataFeed
61
+
37
62
  == TODO
38
63
 
39
- * Tests, documentation
40
- * Filters
64
+ * More tests, documentation.
41
65
  * Access to the management data feed.
42
66
  * Auto-follow of paginated result sets.
43
67
 
data/Rakefile CHANGED
@@ -36,14 +36,7 @@ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
36
  spec.rcov = true
37
37
  end
38
38
 
39
- task :default => :spec
40
-
41
- require 'rdoc/task'
42
- Rake::RDocTask.new do |rdoc|
43
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new
44
41
 
45
- rdoc.rdoc_dir = 'rdoc'
46
- rdoc.title = "google_analytics_feeds #{version}"
47
- rdoc.rdoc_files.include('README*')
48
- rdoc.rdoc_files.include('lib/**/*.rb')
49
- end
42
+ task :default => :spec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{google_analytics_feeds}
8
- s.version = "0.1.1"
8
+ s.version = "0.1.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = [%q{Roland Swingler}]
12
- s.date = %q{2012-08-31}
12
+ s.date = %q{2012-10-16}
13
13
  s.description = %q{Fairly low-level client library to access Google Analytics Feeds}
14
14
  s.email = %q{roland.swingler@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.files = [
20
20
  ".document",
21
21
  ".rspec",
22
+ ".travis.yml",
23
+ ".yardopts",
22
24
  "Gemfile",
23
25
  "LICENSE.txt",
24
26
  "README.rdoc",
@@ -26,9 +28,9 @@ Gem::Specification.new do |s|
26
28
  "VERSION",
27
29
  "google_analytics_feeds.gemspec",
28
30
  "lib/google_analytics_feeds.rb",
29
- "lib/sample.rb",
30
31
  "spec/google_analytics_feeds_spec.rb",
31
32
  "spec/request_spec.rb",
33
+ "spec/session_spec.rb",
32
34
  "spec/spec_helper.rb"
33
35
  ]
34
36
  s.homepage = %q{http://github.com/knaveofdiamonds/google_analytics_feeds}
@@ -45,7 +47,7 @@ Gem::Specification.new do |s|
45
47
  s.add_runtime_dependency(%q<faraday>, [">= 0"])
46
48
  s.add_runtime_dependency(%q<addressable>, [">= 0"])
47
49
  s.add_development_dependency(%q<rspec>, ["~> 2"])
48
- s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
50
+ s.add_development_dependency(%q<yard>, [">= 0"])
49
51
  s.add_development_dependency(%q<jeweler>, ["~> 1.8"])
50
52
  s.add_development_dependency(%q<rcov>, [">= 0"])
51
53
  else
@@ -53,7 +55,7 @@ Gem::Specification.new do |s|
53
55
  s.add_dependency(%q<faraday>, [">= 0"])
54
56
  s.add_dependency(%q<addressable>, [">= 0"])
55
57
  s.add_dependency(%q<rspec>, ["~> 2"])
56
- s.add_dependency(%q<rdoc>, ["~> 3.12"])
58
+ s.add_dependency(%q<yard>, [">= 0"])
57
59
  s.add_dependency(%q<jeweler>, ["~> 1.8"])
58
60
  s.add_dependency(%q<rcov>, [">= 0"])
59
61
  end
@@ -62,7 +64,7 @@ Gem::Specification.new do |s|
62
64
  s.add_dependency(%q<faraday>, [">= 0"])
63
65
  s.add_dependency(%q<addressable>, [">= 0"])
64
66
  s.add_dependency(%q<rspec>, ["~> 2"])
65
- s.add_dependency(%q<rdoc>, ["~> 3.12"])
67
+ s.add_dependency(%q<yard>, [">= 0"])
66
68
  s.add_dependency(%q<jeweler>, ["~> 1.8"])
67
69
  s.add_dependency(%q<rcov>, [">= 0"])
68
70
  end
@@ -3,17 +3,35 @@ require 'addressable/uri'
3
3
  require 'stringio'
4
4
  require 'faraday'
5
5
 
6
+ # @api public
6
7
  module GoogleAnalyticsFeeds
8
+ # Raised if login fails.
9
+ #
10
+ # @api public
7
11
  class AuthenticationError < StandardError ; end
12
+
13
+ # Raised if there is an HTTP-level problem retrieving reports.
14
+ #
15
+ # @api public
8
16
  class HttpError < StandardError ; end
9
17
 
18
+ # A Google Analytics session, used to retrieve reports.
19
+ # @api public
10
20
  class Session
21
+ # @api private
11
22
  CLIENT_LOGIN_URI = "https://www.google.com/accounts/ClientLogin"
12
23
 
24
+ # Creates a new session.
25
+ #
26
+ # Optionally pass a Faraday connection, otherwise uses the default
27
+ # Faraday connection.
13
28
  def initialize(connection=Faraday.default_connection)
14
29
  @connection = connection
15
30
  end
16
31
 
32
+ # Log in to Google Analytics with username and password
33
+ #
34
+ # This should be done before attempting to fetch any reports.
17
35
  def login(username, password)
18
36
  return @token if @token
19
37
  response = @connection.post(CLIENT_LOGIN_URI,
@@ -30,6 +48,10 @@ module GoogleAnalyticsFeeds
30
48
  end
31
49
  end
32
50
 
51
+ # Retrieve a report from Google Analytics.
52
+ #
53
+ # Rows are yielded to a RowHandler, provided either as a class,
54
+ # instance or a block.
33
55
  def fetch_report(report, handler=nil, &block)
34
56
  handler = block if handler.nil?
35
57
  response = report.retrieve(@token, @connection)
@@ -46,17 +68,32 @@ module GoogleAnalyticsFeeds
46
68
  #
47
69
  # Extend this class and override the methods you care about to
48
70
  # handle data feed row data.
71
+ #
72
+ # @abstract
73
+ # @api public
49
74
  class RowHandler
75
+ # Called before any row is parsed.
76
+ #
77
+ # By default, does nothing.
50
78
  def start_rows
51
79
  end
52
80
 
81
+ # Called when each row is parsed.
82
+ #
83
+ # By default, does nothing.
84
+ #
85
+ # @param row Hash
53
86
  def row(row)
54
87
  end
55
88
 
89
+ # Called after all rows have been parsed.
90
+ #
91
+ # By default, does nothing.
56
92
  def end_rows
57
93
  end
58
94
  end
59
95
 
96
+ # @api private
60
97
  module Naming
61
98
  # Returns a ruby-friendly symbol from a google analytics name.
62
99
  #
@@ -81,6 +118,8 @@ module GoogleAnalyticsFeeds
81
118
 
82
119
  # Parses rows from the GA feed via SAX. Clients shouldn't have to
83
120
  # use this - use a RowHandler instead.
121
+ #
122
+ # @api private
84
123
  class RowParser < ::Ox::Sax
85
124
  include Naming
86
125
 
@@ -133,6 +172,9 @@ module GoogleAnalyticsFeeds
133
172
  end
134
173
  end
135
174
 
175
+ # Construct filters for a DataFeed.
176
+ #
177
+ # @api private
136
178
  class FilterBuilder
137
179
  include Naming
138
180
 
@@ -194,6 +236,7 @@ module GoogleAnalyticsFeeds
194
236
  end
195
237
  end
196
238
 
239
+ # @api public
197
240
  class DataFeed
198
241
  include Naming
199
242
 
@@ -203,24 +246,44 @@ module GoogleAnalyticsFeeds
203
246
  @params = {}
204
247
  end
205
248
 
249
+ # Sets the profile id from which this report should be based.
250
+ #
251
+ # @return [GoogleAnalyticsFeeds::DataFeed] a cloned DataFeed.
206
252
  def profile(id)
207
253
  clone_and_set {|params|
208
254
  params['ids'] = symbol_to_name(id)
209
255
  }
210
256
  end
211
257
 
258
+ # Sets the metrics for a query.
259
+ #
260
+ # A query must have at least 1 metric for GA to consider it
261
+ # valid. GA also imposes a maximum (as of writing 10 metrics) per
262
+ # query.
263
+ #
264
+ # @param names [*Symbol] the ruby-style names of the dimensions.
265
+ # @return [GoogleAnalyticsFeeds::DataFeed] a cloned DataFeed.
212
266
  def metrics(*vals)
213
267
  clone_and_set {|params|
214
268
  params['metrics'] = vals.map {|v| symbol_to_name(v) }.join(',')
215
269
  }
216
270
  end
217
271
 
218
- def dimensions(*vals)
272
+ # Sets the dimensions for a query.
273
+ #
274
+ # A query doesn't have to have any dimensions; Google Analytics
275
+ # limits you to 7 dimensions per-query at time of writing.
276
+ #
277
+ # @param names [*Symbol] the ruby-style names of the dimensions.
278
+ # @return [GoogleAnalyticsFeeds::DataFeed] a cloned DataFeed.
279
+ def dimensions(*names)
219
280
  clone_and_set {|params|
220
- params['dimensions'] = vals.map {|v| symbol_to_name(v) }.join(',')
281
+ params['dimensions'] = names.map {|v| symbol_to_name(v) }.join(',')
221
282
  }
222
283
  end
223
284
 
285
+ # Sets the start and end date for retrieved results
286
+ # @return [GoogleAnalyticsFeeds::DataFeed] a cloned DataFeed.
224
287
  def dates(start_date, end_date)
225
288
  clone_and_set {|params|
226
289
  params['start-date'] = start_date.strftime("%Y-%m-%d")
@@ -228,25 +291,58 @@ module GoogleAnalyticsFeeds
228
291
  }
229
292
  end
230
293
 
294
+ # Sets the start index for retrieved results
295
+ # @return [GoogleAnalyticsFeeds::DataFeed]
231
296
  def start_index(i)
232
297
  clone_and_set {|params|
233
298
  params['start-index'] = i.to_s
234
299
  }
235
300
  end
236
301
 
302
+ # Sets the maximum number of results retrieved.
303
+ #
304
+ # Google Analytics has its own maximum as well.
305
+ # @return [GoogleAnalyticsFeeds::DataFeed]
237
306
  def max_results(i)
238
307
  clone_and_set {|params|
239
308
  params['max-results'] = i.to_s
240
309
  }
241
310
  end
242
311
 
312
+ # Filter the result set, based on the results of a block.
313
+ #
314
+ # All the block methods follow the form operator(name,
315
+ # value). Supported operators include: eql, not_eql, lt, lte, gt,
316
+ # gte, contains, not_contains, match and not_match - hopefully all
317
+ # self-explainatory.
318
+ #
319
+ # Example:
320
+ #
321
+ # query.
322
+ # filter {
323
+ # eql(:dimension, "value")
324
+ # gte(:metric, 3)
325
+ # }
326
+ #
327
+ # @return [GoogleAnalyticsFeeds::DataFeed]
243
328
  def filters(&block)
244
- builder =
245
329
  clone_and_set {|params|
246
330
  params['filters'] = FilterBuilder.new.build(&block)
247
331
  }
248
332
  end
249
333
 
334
+ # Use a dynamic advanced segment.
335
+ #
336
+ # Block methods follow the same style as for filters. Named
337
+ # advanced segments are not yet supported.
338
+ #
339
+ # @return [GoogleAnalyticsFeeds::DataFeed]
340
+ def segment(&block)
341
+ clone_and_set {|params|
342
+ params['segment'] = "dynamic::" + FilterBuilder.new.build(&block)
343
+ }
344
+ end
345
+
250
346
  # Sorts the result set by a column.
251
347
  #
252
348
  # Direction can be :asc or :desc.
@@ -257,14 +353,16 @@ module GoogleAnalyticsFeeds
257
353
  }
258
354
  end
259
355
 
356
+ # Returns the URI string needed to retrieve this report.
260
357
  def uri
261
358
  uri = Addressable::URI.parse(BASE_URI)
262
359
  uri.query_values = @params
263
- uri.to_s.gsub("%40", "~")
360
+ uri.to_s.gsub("%40", "@")
264
361
  end
265
362
 
266
363
  alias :to_s :uri
267
364
 
365
+ # @api private
268
366
  def retrieve(session_token, connection)
269
367
  connection.get(uri) do |request|
270
368
  request.headers['Authorization'] =
@@ -272,6 +370,7 @@ module GoogleAnalyticsFeeds
272
370
  end
273
371
  end
274
372
 
373
+ # @api private
275
374
  def clone
276
375
  obj = super
277
376
  obj.instance_variable_set(:@params, @params.clone)
@@ -291,6 +390,7 @@ module GoogleAnalyticsFeeds
291
390
  end
292
391
  end
293
392
 
393
+ # @api private
294
394
  class DataFeedParser
295
395
  def initialize(handler)
296
396
  if handler.kind_of?(Proc)
@@ -57,4 +57,16 @@ describe GoogleAnalyticsFeeds::DataFeed do
57
57
  "filters" => "ga:baz==4;ga:foo=@123"
58
58
  }
59
59
  end
60
+
61
+ it "can add a dynamic segment" do
62
+ feed = described_class.new.
63
+ segment {
64
+ eql :medium, "referral"
65
+ }
66
+
67
+ uri = Addressable::URI.parse(feed.uri)
68
+ uri.query_values.should == {
69
+ "segment" => "dynamic::ga:medium==referral"
70
+ }
71
+ end
60
72
  end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe GoogleAnalyticsFeeds::Session do
4
+ it "raises an AuthenticationError if login is not a success" do
5
+ connection = stub(:connection, :post => stub(:response, :success? => false))
6
+ expect {
7
+ described_class.new(connection).login('name', 'password')
8
+ }.to raise_error(GoogleAnalyticsFeeds::AuthenticationError)
9
+ end
10
+
11
+ it "sets and returns a token if login is a success" do
12
+ connection = stub(:connection, :post => stub(:response, :success? => true, :body => "Auth=MYTOKEN"))
13
+ described_class.new(connection).login('name', 'password').should == "MYTOKEN"
14
+ end
15
+
16
+ it "posts the username and email to Google Analytics" do
17
+ connection = mock(:connection)
18
+ connection.should_receive(:post).
19
+ with("https://www.google.com/accounts/ClientLogin",
20
+ 'Email' => 'me@example.com',
21
+ 'Passwd' => 'password',
22
+ 'accountType' => 'HOSTED_OR_GOOGLE',
23
+ 'service' => 'analytics',
24
+ 'source' => 'ruby-google-analytics-feeds').
25
+ and_return(stub(:response, :success? => true, :body => "Auth=MYTOKEN"))
26
+
27
+ described_class.new(connection).login('me@example.com', 'password')
28
+ end
29
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google_analytics_feeds
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Roland Swingler
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-08-31 00:00:00 Z
18
+ date: 2012-10-16 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  requirement: &id001 !ruby/object:Gem::Requirement
@@ -77,15 +77,14 @@ dependencies:
77
77
  requirement: &id005 !ruby/object:Gem::Requirement
78
78
  none: false
79
79
  requirements:
80
- - - ~>
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- hash: 31
82
+ hash: 3
83
83
  segments:
84
- - 3
85
- - 12
86
- version: "3.12"
84
+ - 0
85
+ version: "0"
87
86
  version_requirements: *id005
88
- name: rdoc
87
+ name: yard
89
88
  prerelease: false
90
89
  type: :development
91
90
  - !ruby/object:Gem::Dependency
@@ -129,6 +128,8 @@ extra_rdoc_files:
129
128
  files:
130
129
  - .document
131
130
  - .rspec
131
+ - .travis.yml
132
+ - .yardopts
132
133
  - Gemfile
133
134
  - LICENSE.txt
134
135
  - README.rdoc
@@ -136,9 +137,9 @@ files:
136
137
  - VERSION
137
138
  - google_analytics_feeds.gemspec
138
139
  - lib/google_analytics_feeds.rb
139
- - lib/sample.rb
140
140
  - spec/google_analytics_feeds_spec.rb
141
141
  - spec/request_spec.rb
142
+ - spec/session_spec.rb
142
143
  - spec/spec_helper.rb
143
144
  homepage: http://github.com/knaveofdiamonds/google_analytics_feeds
144
145
  licenses:
@@ -1,15 +0,0 @@
1
- require File.dirname(__FILE__) + "/google_analytics_feeds"
2
-
3
- class LoggingRowHandler < GoogleAnalyticsFeeds::RowHandler
4
- def row(r)
5
- puts r.inspect
6
- end
7
-
8
- def end_rows
9
- puts "Done!"
10
- end
11
- end
12
-
13
- File.open("/home/roland/gaout2.xml") do |fh|
14
- GoogleAnalyticsFeeds.parse_data_rows(fh, LoggingRowHandler)
15
- end