google_analytics_feeds 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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