analytics-rb 0.0.10

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 (42) hide show
  1. data/.coveralls.yml +3 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +20 -0
  5. data/README.md +71 -0
  6. data/Rakefile +11 -0
  7. data/analytics-rb.gemspec +26 -0
  8. data/fixtures/vcr_cassettes/assets_up-fluence.yml +99 -0
  9. data/fixtures/vcr_cassettes/assets_up-fluence/management/accounts.yml +52 -0
  10. data/lib/analytics-rb.rb +1 -0
  11. data/lib/analytics.rb +37 -0
  12. data/lib/analytics/account.rb +28 -0
  13. data/lib/analytics/client.rb +35 -0
  14. data/lib/analytics/configurable.rb +22 -0
  15. data/lib/analytics/core_ext/string.rb +5 -0
  16. data/lib/analytics/error.rb +12 -0
  17. data/lib/analytics/error/no_access_token_provided.rb +6 -0
  18. data/lib/analytics/error/permission_insufficient.rb +7 -0
  19. data/lib/analytics/oauth.rb +27 -0
  20. data/lib/analytics/oauth2.rb +27 -0
  21. data/lib/analytics/profile.rb +23 -0
  22. data/lib/analytics/renderer/hashed.rb +25 -0
  23. data/lib/analytics/renderer/plain.rb +9 -0
  24. data/lib/analytics/report.rb +59 -0
  25. data/lib/analytics/request.rb +19 -0
  26. data/lib/analytics/version.rb +3 -0
  27. data/lib/analytics/web_property.rb +26 -0
  28. data/spec/analytics/account_spec.rb +17 -0
  29. data/spec/analytics/client_spec.rb +25 -0
  30. data/spec/analytics/core_ext/string_spec.rb +13 -0
  31. data/spec/analytics/profile_spec.rb +19 -0
  32. data/spec/analytics/renderer/hashed_spec.rb +19 -0
  33. data/spec/analytics/report_spec.rb +21 -0
  34. data/spec/analytics/request_spec.rb +33 -0
  35. data/spec/analytics/web_propery_spec.rb +18 -0
  36. data/spec/analytics_spec.rb +44 -0
  37. data/spec/spec_helper.rb +16 -0
  38. data/spec/support/management_profiles.rb +1 -0
  39. data/spec/support/management_web_properties.rb +1 -0
  40. data/spec/support/manangement_accounts.rb +2 -0
  41. data/spec/support/permission_error.rb +1 -0
  42. metadata +228 -0
@@ -0,0 +1,3 @@
1
+ service_name: travis-pro
2
+ repo_token: oGPYHZPw9CvkHSCdoSy4faX4Be7zMGKEB
3
+
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ <<<<<<< HEAD
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+
20
+ # YARD artifacts
21
+ .yardoc
22
+ _yardoc
23
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in analytics.gemspec
4
+ gemspec
5
+
6
+ gem 'coveralls', require: false
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Alexis Montagne
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,71 @@
1
+ # Analytics
2
+
3
+ [![Build Status](https://travis-ci.org/AlexisMontagne/analytics.png?branch=master)](https://travis-ci.org/AlexisMontagne/analytics)
4
+ [![Code Climate](https://codeclimate.com/repos/528d2778c7f3a335fa013259/badges/c4c17ae9f5591d5b5509/gpa.png)](https://codeclimate.com/repos/528d2778c7f3a335fa013259/feed)
5
+ [![githalytics.com alpha](https://cruel-carlota.pagodabox.com/b9b735f530731e985ae3f291b84c4e63 "githalytics.com")](http://githalytics.com/AlexisMontagne/analytics)
6
+
7
+ A Ruby interface to the Google Analytics API.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'analytics-rb'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install analytics-rb
22
+
23
+ ## Usage
24
+
25
+ ### Authentification
26
+
27
+ To make calls to the Google Analytics API, you have to authenticate via an API key (which you can get from the Google [APIs Console](https://code.google.com/apis/console#access). To set your API key and get started, use:
28
+
29
+ Analytics.consumer_key = 'your key'
30
+ Analytics.consumer_secret = 'your secret'
31
+
32
+ Then you have to provide an access token ( OAuth or OAuth2 ) to manage or visualize statistics.
33
+
34
+ client = Analytics::Client.new(Analytics::OAuth.access_token('my token', 'my secret')) # With OAuth 1.x
35
+ client = Analytics::Client.new(Analytics::OAuth.access_token('my token')) # With OAuth 2.x
36
+
37
+ ### Management
38
+
39
+ ** You can access to the accounts, web properties and profile from a `Analytics::Client`
40
+
41
+ client.accounts # returns an array of Analytics::Account
42
+ client.web_properties # returns an array of Analytics::WebProperty
43
+ client.profiles # returns an array of Analytics::Profile
44
+
45
+ ### Reporting
46
+
47
+ From a `Analyics::Profile` you can access to the report object
48
+
49
+ profile.report
50
+ # or
51
+ Analyics::Report.new('profile_id', access_token) # access_token is a OAuth::AccessToken or OAuth2::AccessToken
52
+
53
+ You can grab datas with a simple DSL
54
+
55
+ report.visits(0, Time.now) # it should grab all visits from Jan 1st 1970 to now
56
+ # you can provide all metrics from google analytics ( visitors, new_visits, percent_new_visits, ... )
57
+
58
+ # you can provide a metric and a dimension
59
+ report.visits_by_month(0, Time.now)
60
+
61
+ # you can provide multiple metrics and dimensions as well
62
+ report.visists_and_visitors_by_month_and_city(0, Time.now)
63
+
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create new Pull Request
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ task :default => :spec
6
+
7
+ require 'rspec/core/rake_task'
8
+
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.rspec_opts = ["--color", '--format doc']
11
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/analytics/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Alexis Montagne"]
6
+ gem.email = ["alexis.montagne@gmail.com"]
7
+ gem.description = %q{A Ruby interface to the Google Analytics API}
8
+ gem.summary = %q{Basic implementation of an ruby interface to the Analytics API}
9
+ gem.homepage = "http://www.github.com/AlexisMontagne/analytics"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "analytics-rb"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Analytics::VERSION
17
+
18
+ gem.add_runtime_dependency("multi_json")
19
+ gem.add_runtime_dependency("oauth2")
20
+ gem.add_runtime_dependency("oauth")
21
+ gem.add_development_dependency("rspec")
22
+ gem.add_development_dependency("rake")
23
+ gem.add_development_dependency("mocha")
24
+ gem.add_development_dependency("vcr")
25
+ gem.add_development_dependency("fakeweb")
26
+ end
@@ -0,0 +1,99 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://assets.up-fluence.com/test.json
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.8
12
+ Authorization:
13
+ - Bearer 123
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - ! '*/*'
18
+ response:
19
+ status:
20
+ code: 301
21
+ message: Moved Permanently
22
+ headers:
23
+ Server:
24
+ - nginx/1.4.1
25
+ Date:
26
+ - Tue, 03 Dec 2013 02:42:02 GMT
27
+ Transfer-Encoding:
28
+ - chunked
29
+ Connection:
30
+ - keep-alive
31
+ Cache-Control:
32
+ - no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0
33
+ Location:
34
+ - http://images.up-fluence.com/test.json
35
+ body:
36
+ encoding: US-ASCII
37
+ string: ! '<html><head><title>301 Moved Permanently</title><script type=''text/javascript''> var
38
+ _gaq = _gaq || []; _gaq.push([''_setAccount'', ''UA-41488471-3'']); _gaq.push([''_trackPageview'']); (function()
39
+ { var ga = document.createElement(''script''); ga.type
40
+ = ''text/javascript''; ga.async = true; ga.src = (''https:''
41
+ == document.location.protocol ? ''https://ssl'' : ''http://www'') + ''.google-analytics.com/ga.js''; var
42
+ s = document.getElementsByTagName(''script'')[0]; s.parentNode.insertBefore(ga,
43
+ s); })(); </script> </head>
44
+
45
+ <body bgcolor="white">
46
+
47
+ <center><h1>301 Moved Permanently</h1></center>
48
+
49
+ <hr><center>nginx</center>
50
+
51
+ </body>
52
+
53
+ </html>'
54
+ http_version:
55
+ recorded_at: Tue, 03 Dec 2013 02:42:02 GMT
56
+ - request:
57
+ method: get
58
+ uri: http://images.up-fluence.com/test.json
59
+ body:
60
+ encoding: US-ASCII
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - Faraday v0.8.8
65
+ Authorization:
66
+ - Bearer 123
67
+ Accept-Encoding:
68
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
69
+ Accept:
70
+ - ! '*/*'
71
+ response:
72
+ status:
73
+ code: 200
74
+ message: OK
75
+ headers:
76
+ Server:
77
+ - nginx/1.4.1
78
+ Date:
79
+ - Tue, 03 Dec 2013 02:42:03 GMT
80
+ Content-Type:
81
+ - application/json
82
+ Content-Length:
83
+ - '17'
84
+ Last-Modified:
85
+ - Sun, 28 Jul 2013 12:06:12 GMT
86
+ Connection:
87
+ - keep-alive
88
+ Etag:
89
+ - ! '"51f50934-11"'
90
+ Accept-Ranges:
91
+ - bytes
92
+ body:
93
+ encoding: US-ASCII
94
+ string: ! '{"foo" => "bar"}
95
+
96
+ '
97
+ http_version:
98
+ recorded_at: Tue, 03 Dec 2013 02:42:02 GMT
99
+ recorded_with: VCR 2.4.0
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://www.googleapis.com/analytics/v3/management/accounts
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.8
12
+ Authorization:
13
+ - Bearer 123
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - ! '*/*'
18
+ response:
19
+ status:
20
+ code: 401
21
+ message: Unauthorized
22
+ headers:
23
+ Www-Authenticate:
24
+ - Bearer realm="https://www.google.com/accounts/AuthSubRequest", error=invalid_token
25
+ Content-Type:
26
+ - application/json; charset=UTF-8
27
+ Date:
28
+ - Tue, 03 Dec 2013 02:42:03 GMT
29
+ Expires:
30
+ - Tue, 03 Dec 2013 02:42:03 GMT
31
+ Cache-Control:
32
+ - private, max-age=0
33
+ X-Content-Type-Options:
34
+ - nosniff
35
+ X-Frame-Options:
36
+ - SAMEORIGIN
37
+ X-Xss-Protection:
38
+ - 1; mode=block
39
+ Server:
40
+ - GSE
41
+ Alternate-Protocol:
42
+ - 443:quic
43
+ Transfer-Encoding:
44
+ - chunked
45
+ body:
46
+ encoding: US-ASCII
47
+ string: ! '{"error":{"errors":[{"domain":"global","reason":"authError","message":"Invalid
48
+ Credentials","locationType":"header","location":"Authorization"}],"code":401,"message":"Invalid
49
+ Credentials"}}'
50
+ http_version:
51
+ recorded_at: Tue, 03 Dec 2013 02:42:03 GMT
52
+ recorded_with: VCR 2.4.0
@@ -0,0 +1 @@
1
+ require 'analytics'
@@ -0,0 +1,37 @@
1
+ require "analytics/version"
2
+ require "analytics/configurable"
3
+ require "analytics/oauth2"
4
+ require "analytics/oauth"
5
+ require "analytics/client"
6
+ require "analytics/account"
7
+ require "analytics/web_property"
8
+ require "analytics/profile"
9
+ require "analytics/report"
10
+ require "analytics/request"
11
+ require "analytics/error"
12
+ require "analytics/error/permission_insufficient"
13
+ require "analytics/error/no_access_token_provided"
14
+ require "analytics/core_ext/string"
15
+ require "analytics/renderer/plain"
16
+ require "analytics/renderer/hashed"
17
+
18
+ module Analytics
19
+ BASE_URL = 'https://www.googleapis.com/analytics/v3'
20
+
21
+ class << self
22
+ include Analytics::Configurable
23
+
24
+ def client
25
+ @client ||= Analytics::Client.new(options.fetch(:default_access_token))
26
+ end
27
+
28
+ def from_token(oauth_token)
29
+ @client = Analytics::Client.new(oauth_token)
30
+ end
31
+
32
+ private
33
+ def method_missing(name, *args, &block)
34
+ client.respond_to?(name) ? client.send(name, args, block) : super
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ module Analytics
2
+ class Account
3
+ attr_reader :name, :id
4
+
5
+ def initialize(opts, access_token)
6
+ @name = opts.fetch("name")
7
+ @id = opts.fetch("id")
8
+ @access_token = access_token
9
+ end
10
+
11
+ def web_properties
12
+ @web_properties ||= request_web_properties
13
+ end
14
+
15
+ def flush!
16
+ @web_properties = nil
17
+ end
18
+
19
+ private
20
+ def request_web_properties
21
+ Analytics::Request.new(Analytics::BASE_URL, "management/accounts/#{@id}/webproperties", @access_token).response["items"].map do |item|
22
+ Analytics::WebProperty.new(item, @access_token) rescue nil
23
+ end.compact
24
+ rescue
25
+ []
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ module Analytics
2
+ class Client
3
+ attr_reader :access_token
4
+
5
+ def initialize(access_token)
6
+ @access_token = access_token
7
+ raise Analytics::Error::NoAccessTokenProvided if @access_token.nil?
8
+ end
9
+
10
+ def accounts
11
+ @accounts ||= request_accounts
12
+ end
13
+
14
+ def web_properties
15
+ accounts.map(&:web_properties).flatten
16
+ end
17
+
18
+ def profiles
19
+ web_properties.map(&:profiles).flatten
20
+ end
21
+
22
+ def flush!
23
+ @accounts = nil
24
+ end
25
+
26
+ private
27
+ def request_accounts
28
+ Analytics::Request.new(Analytics::BASE_URL, 'management/accounts', @access_token).response["items"].map do |item|
29
+ Analytics::Account.new(item, @access_token) rescue nil
30
+ end.compact
31
+ rescue
32
+ []
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ require 'forwardable'
2
+
3
+ module Analytics
4
+ module Configurable
5
+ extend Forwardable
6
+
7
+ attr_accessor :consumer_key, :consumer_secret, :default_access_token
8
+
9
+ def configure
10
+ yield self
11
+ self
12
+ end
13
+
14
+ def options
15
+ {
16
+ :consumer_key => consumer_key,
17
+ :consumer_secret => consumer_secret,
18
+ :default_access_token => default_access_token
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_camelcase(first_letter = :upper)
3
+ self.downcase.gsub(/_\w/) { |x| x[1..1].upcase }.gsub(/^\w/) { |x| first_letter.eql?(:upper) ? x.upcase : x }
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Analytics
2
+ class Error < StandardError
3
+ def initialize(exception=$!, response_headers={})
4
+ @wrapped_exception = exception
5
+ exception.respond_to?(:backtrace) ? super(exception.message) : super(exception.to_s)
6
+ end
7
+
8
+ def backtrace
9
+ @wrapped_exception.respond_to?(:backtrace) ? @wrapped_exception.backtrace : super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module Analytics
2
+ class Error
3
+ class NoAccessTokenProvided < Analytics::Error
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Analytics
2
+ class Error
3
+ class PermissionInsufficient < Analytics::Error
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ require "forwardable"
2
+ require "oauth"
3
+
4
+ module Analytics
5
+ class OAuth
6
+ class << self
7
+ def oauth_consumer
8
+ @consumer ||= ::OAuth::Consumer.new(
9
+ Analytics.consumer_key,
10
+ Analytics.consumer_secret, {
11
+ :site => 'https://www.google.com',
12
+ :request_token_path => '/accounts/OAuthGetRequestToken',
13
+ :access_token_path => '/accounts/OAuthGetAccessToken',
14
+ :authorize_path => '/accounts/OAuthAuthorizeToken'
15
+ })
16
+ end
17
+
18
+ def access_token(token, secret = nil, opts = {})
19
+ if token.is_a? ::OAuth::AccessToken
20
+ token
21
+ else
22
+ ::OAuth::AccessToken.new(oauth_consumer, token, secret)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'forwardable'
2
+ require 'oauth2'
3
+ module Analytics
4
+ class OAuth2
5
+ class << self
6
+ def oauth_client
7
+ @oauth_client ||= ::OAuth2::Client.new(
8
+ Analytics.consumer_key,
9
+ Analytics.consumer_secret,
10
+ :site => 'https://accounts.google.com',
11
+ :authorize_url => '/o/oauth2/auth',
12
+ :token_url => '/o/oauth2/token'
13
+ )
14
+ end
15
+
16
+ def access_token(token, opts = {})
17
+ if token.nil?
18
+ raise Analytics::Error::NoAccessTokenProvided
19
+ elsif token.is_a? ::OAuth2::AccessToken
20
+ token
21
+ else
22
+ ::OAuth2::AccessToken.new(oauth_client, token, opts)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Analytics
2
+ class Profile
3
+ attr_reader :web_property_id, :name, :id, :url, :account_id
4
+
5
+ def initialize(opts, access_token)
6
+ @access_token = access_token
7
+ @name = opts.fetch("name")
8
+ @id = opts.fetch("id")
9
+ @url = opts.fetch("websiteUrl")
10
+ @web_property_id = opts.fetch("webPropertyId")
11
+ @account_id = opts.fetch("accountId")
12
+ end
13
+
14
+ def report
15
+ @report ||= Analytics::Report.new(@id, @access_token)
16
+ end
17
+
18
+ private
19
+ def method_missing(method_name, *args, &block)
20
+ report.send(method_name, args, block)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Analytics
2
+ module Renderer
3
+ class Hashed
4
+ def self.render(data, metrics, dimensions)
5
+ if (dimensions + metrics).length > 1
6
+ data.map do |res|
7
+ apply!(data, metrics, dimensions)
8
+ end
9
+ else
10
+ data.first
11
+ end
12
+ end
13
+
14
+ def self.apply!(data, metrics, dimensions)
15
+ elt, itr = {}, 0
16
+ (dimensions + metrics).each do |d|
17
+ elt[d] = data[itr]
18
+ itr += 1
19
+ end
20
+ elt
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,9 @@
1
+ module Analytics
2
+ module Renderer
3
+ class Plain
4
+ def self.render(data, metrics, dimensions)
5
+ data
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,59 @@
1
+ module Analytics
2
+ class Report
3
+ METRICS = [
4
+ :visitors, :new_visits, :percent_new_visits,
5
+ :visits, :bounces, :pageviews
6
+ ]
7
+
8
+ DIMENSIONS = [
9
+ :continent, :city, :country, :date, :year,
10
+ :month, :week, :day, :hour
11
+ ]
12
+
13
+
14
+ attr_reader :id, :access_token
15
+
16
+ def initialize(id, access_token, renderer = Analytics::Renderer::Plain)
17
+ @id = id
18
+ @access_token = access_token
19
+ @renderer = renderer
20
+ end
21
+
22
+ def get(start_date, end_date, metrics, dimensions = [])
23
+ response = Analytics::Request.new(Analytics::BASE_URL, get_url(start_date, end_date, metrics, dimensions), @access_token).response
24
+ return nil unless response["rows"]
25
+ response["rows"].map do |row|
26
+ @renderer.render(row, metrics, dimensions)
27
+ end
28
+ end
29
+
30
+ private
31
+ def get_url(start_date, end_date, metrics, dimensions)
32
+ str = "data/ga?ids=ga:#{@id}&start-date=#{start_date.strftime("%F")}&end-date=#{end_date.strftime("%F")}&metrics=#{to_ga(metrics)}"
33
+ str += "&dimensions=#{to_ga(dimensions)}" unless dimensions.empty?
34
+ str
35
+ end
36
+
37
+ def split_by(method, det = 'and')
38
+ method.to_s.split("_#{det}_").map(&:to_sym)
39
+ end
40
+
41
+ def to_ga(array)
42
+ array.map { |x| 'ga:' + x.to_s.to_camelcase(:lower) }.join(",")
43
+ end
44
+
45
+ def method_missing(m, *args, &block)
46
+ args.flatten!
47
+ if METRICS.include? m.to_sym
48
+ get(args[0],args[1], [m.to_sym])
49
+ elsif m.to_s.split('_by_').length.eql? 2
50
+ lhs, rhs = split_by(m, 'by')
51
+ get(args[0],args[1], split_by(lhs), split_by(rhs))
52
+ elsif split_by(m).length.eql? 2
53
+ get(args[0],args[1], split_by(m), [])
54
+ else
55
+ super
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ require "multi_json"
2
+
3
+ module Analytics
4
+ class Request < Struct.new(:base_url, :relative_path, :access_token)
5
+ def full_path
6
+ "#{base_url.to_s}/#{relative_path.to_s}"
7
+ end
8
+
9
+ def request
10
+ access_token.get(full_path).body
11
+ rescue ::OAuth2::Error
12
+ raise Analytics::Error::PermissionInsufficient
13
+ end
14
+
15
+ def response
16
+ @response ||= MultiJson.load(request)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Analytics
2
+ VERSION = "0.0.10"
3
+ end
@@ -0,0 +1,26 @@
1
+ module Analytics
2
+ class WebProperty
3
+ attr_reader :id, :name, :account_id, :url
4
+
5
+ def initialize(opts, access_token)
6
+ @access_token = access_token
7
+ @id = opts.fetch("id")
8
+ @name = opts.fetch("name", "Not found")
9
+ @account_id = opts.fetch("accountId")
10
+ @url = opts.fetch("websiteUrl", "Not found")
11
+ end
12
+
13
+ def profiles
14
+ @profiles ||= request_profiles
15
+ end
16
+
17
+ private
18
+ def request_profiles
19
+ Analytics::Request.new(Analytics::BASE_URL, "management/accounts/#{@account_id}/webproperties/#{@id}/profiles", @access_token).response["items"].map do |item|
20
+ Analytics::Profile.new(item, @access_token) rescue nil
21
+ end.compact
22
+ rescue
23
+ []
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::Account do
4
+ context ".web_properties" do
5
+ it "should return a list of web properties" do
6
+ client = Analytics::Account.new({
7
+ "id" => 'foo',
8
+ "name" => 'bar',
9
+ "username" => 'baz'},
10
+ nil)
11
+ Analytics::Request.any_instance.stubs(:request).returns(MANAGEMENT_WEB_PROPERTIES)
12
+ client.web_properties.count.should == 2
13
+ client.web_properties.first.class.should == Analytics::WebProperty
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::Client do
4
+ context "#initialize" do
5
+ it "raise error without token provided" do
6
+ expect { Analytics::Client.new(nil) }.to raise_error
7
+ end
8
+ end
9
+
10
+ context ".flush!" do
11
+ it "should set nil to datas" do
12
+ client = Analytics::Client.new('data')
13
+ client.stubs(:request_accounts).returns([1, 2])
14
+ ex = client.accounts
15
+ client.flush!
16
+ client.accounts.should == ex
17
+ end
18
+ end
19
+
20
+ context ".accounts" do
21
+ client = Analytics::Client.new('data')
22
+ Analytics::Request.any_instance.stubs(:request).returns(MANAGEMENT_ACCOUNTS)
23
+ client.accounts.count.should == 1
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe String do
4
+ context ".camelize" do
5
+ it "should transform _l to L" do
6
+ "foo_bar".to_camelcase.should == "FooBar"
7
+ end
8
+
9
+ it "should do the same on lower case" do
10
+ "foo_bar".to_camelcase(:lower).should == "fooBar"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::Profile do
4
+ context ".report" do
5
+ it "should forward methods to the report linked" do
6
+ client = Analytics::Profile.new({
7
+ "id" => 'foo',
8
+ "name" => 'bar',
9
+ "accountId" => 'baz',
10
+ "websiteUrl" => 'http://www.gmail.com',
11
+ "webPropertyId" => 'b'},
12
+ '1')
13
+ client.report.stubs(:foo).returns(:bar)
14
+ client.foo.should == :bar
15
+ end
16
+ end
17
+ end
18
+
19
+
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::Renderer::Hashed do
4
+ context ".apply!" do
5
+ it "should push data in the correct column" do
6
+ res = Analytics::Renderer::Hashed.apply!([1, 2, 3, 4], [:m1, :m2], [:m3, :m4])
7
+ res[:m1].should == 3
8
+ res[:m2].should == 4
9
+ res[:m3].should == 1
10
+ res[:m4].should == 2
11
+ end
12
+ end
13
+ context ".render" do
14
+ it "should return the data with it has only a metrics registred" do
15
+ res = Analytics::Renderer::Hashed.render([1],[:m1],[])
16
+ res.should == 1
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::Report do
4
+ context '.get' do
5
+ before :each do
6
+ @report = Analytics::Report.new('1', '2')
7
+ @report.stubs(:get).returns(:bar)
8
+ end
9
+ it "should forwarsd to get with a metrics name" do
10
+ r = @report.visits(Date.today - 5, Date.today).should == :bar
11
+ end
12
+
13
+ it "should forwards with multiples metrics" do
14
+ r = @report.visits_and_visitors(Date.today - 5, Date.today).should == :bar
15
+ end
16
+
17
+ it "should forwards to get with metrics and dimensions" do
18
+ r = @report.visits_by_country(Date.today - 5, Date.today).should == :bar
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::Request do
4
+ let(:incorrect_token) { ::OAuth2::AccessToken.new(::OAuth2::Client.new('1','1'),'123') }
5
+ let(:sample_request) { Analytics::Request.new('http://assets.up-fluence.com', 'test.json', incorrect_token) }
6
+
7
+ context ".full_path" do
8
+ it "should render the normal fullpath" do
9
+ sample_request.full_path.should == 'http://assets.up-fluence.com/test.json'
10
+ end
11
+ end
12
+
13
+ context ".request" do
14
+ it "should return a string with datas" do
15
+ VCR.use_cassette('assets.up-fluence') do
16
+ sample_request.request.should include('{"foo" => "bar"}')
17
+ end
18
+ end
19
+
20
+ it "should throw an error if the oauth token is wrong" do
21
+ VCR.use_cassette('assets.up-fluence/management/accounts') do
22
+ expect { Analytics::Request.new(Analytics::BASE_URL, 'management/accounts', incorrect_token).request }.to raise_error(Analytics::Error::PermissionInsufficient)
23
+ end
24
+ end
25
+ end
26
+
27
+ context ".response" do
28
+ it "should parse JSON" do
29
+ sample_request.stubs(:request).returns(MANAGEMENT_ACCOUNTS)
30
+ sample_request.response.fetch("kind").should == "analytics#accounts"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics::WebProperty do
4
+ context ".profiles" do
5
+ it "should return a list of profile" do
6
+ client = Analytics::WebProperty.new({
7
+ "id" => 'foo',
8
+ "name" => 'bar',
9
+ "accountId" => 'baz',
10
+ "websiteUrl" => 'http://www.gmail.com'},
11
+ nil)
12
+ Analytics::Request.any_instance.stubs(:request).returns(MANAGEMENT_PROFILES)
13
+ client.profiles.count.should == 1
14
+ client.profiles.first.class.should == Analytics::Profile
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe Analytics do
4
+ before(:each) do
5
+ Analytics.configure do |an|
6
+ an.consumer_key = 'foo'
7
+ an.consumer_secret = 'bar'
8
+ an.default_access_token = OAuth2::AccessToken.new('','')
9
+ end
10
+ end
11
+
12
+ context "#Configurable" do
13
+ context ".configure" do
14
+ it "should persist credentials" do
15
+ Analytics.consumer_secret.should == 'bar'
16
+ Analytics.consumer_key.should == 'foo'
17
+ end
18
+ end
19
+
20
+ context ".options" do
21
+ it "should return a hash composed by both keys" do
22
+ Analytics.options.class.should == Hash
23
+ Analytics.options.fetch(:consumer_key).should == 'foo'
24
+ Analytics.options.fetch(:consumer_secret).should == 'bar'
25
+ end
26
+ end
27
+ end
28
+
29
+ context ".client" do
30
+ it "should be a Analytics::Client" do
31
+ Analytics.client.class.should == Analytics::Client
32
+ end
33
+
34
+ it "should stay the same" do
35
+ Analytics.client.should == Analytics.client
36
+ end
37
+ end
38
+
39
+ it "forward methods to the client" do
40
+ Analytics.client.stubs(:foo).returns(:bar)
41
+
42
+ Analytics.client.foo.should == :bar
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ require 'analytics-rb'
2
+ require 'coveralls'
3
+ Coveralls.wear!
4
+
5
+ require 'vcr'
6
+
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
8
+
9
+ VCR.configure do |c|
10
+ c.cassette_library_dir = 'fixtures/vcr_cassettes'
11
+ c.hook_into :fakeweb
12
+ end
13
+
14
+ RSpec.configure do |config|
15
+ config.mock_with :mocha
16
+ end
@@ -0,0 +1 @@
1
+ MANAGEMENT_PROFILES = '{"kind":"analytics#profiles","username":"alexis.montagne@gmail.com","totalResults":1,"startIndex":1,"itemsPerPage":1000,"items":[{"id":"PRO_ID","kind":"analytics#profile","selfLink":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID/profiles/PRO_ID","accountId":"ACC_ID","webPropertyId":"WEB_ID","internalWebPropertyId":"PRO_ID","name":"PRO_NAE","currency":"USD","timezone":"Europe/Paris","websiteUrl":"https://www.foo.baz","type":"WEB","created":"2013-06-05T11:19:31.774Z","updated":"2013-07-09T15:35:30.823Z","eCommerceTracking":false,"parentLink":{"type":"analytics#webproperty","href":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID"},"childLink":{"type":"analytics#goals","href":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID/profiles/PRO_ID/goals"}}]}'
@@ -0,0 +1 @@
1
+ MANAGEMENT_WEB_PROPERTIES = '{"kind":"analytics#webproperties","username":"USERNAME","totalResults":3,"startIndex":1,"itemsPerPage":1000,"items":[{"id":"WEB_ID","kind":"analytics#webproperty","selfLink":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID","accountId":"ACC_ID","internalWebPropertyId":"WEB_ID","name":"name","websiteUrl":"url","level":"STANDARD","profileCount":1,"industryVertical":"ONLINE_COMMUNITIES","created":"2013-06-05T11:19:31.774Z","updated":"2013-06-05T21:43:13.463Z","parentLink":{"type":"analytics#account","href":"https://www.googleapis.com/analytics/v3/management/accounts/ID"},"childLink":{"type":"analytics#profiles","href":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID/profiles"}},{"id":"WEB_ID","kind":"analytics#webproperty","selfLink":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID","accountId":"ACC_ID","internalWebPropertyId":"WEB_ID","name":"NAME","websiteUrl":"URL","level":"STANDARD","profileCount":1,"industryVertical":"ONLINE_COMMUNITIES","created":"2013-06-05T11:23:45.793Z","updated":"2013-06-06T08:10:55.753Z","parentLink":{"type":"analytics#account","href":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID"},"childLink":{"type":"analytics#profiles","href":"https://www.googleapis.com/analytics/v3/management/accounts/ACC_ID/webproperties/WEB_ID/profiles"}}]}'
@@ -0,0 +1,2 @@
1
+ MANAGEMENT_ACCOUNTS = '{"kind":"analytics#accounts","username":"email_address","totalResults":1,"startIndex":1,"itemsPerPage":1000,"items":[{"id":"account_id","kind":"analytics#account","selfLink":"https://www.googleapis.com/analytics/v3/management/accounts/account_id","name":"Account name","created":"2013-06-05T11:19:31.774Z","updated":"2013-07-09T15:35:30.823Z","childLink":{"type":"analytics#webproperties","href":"https://www.googleapis.com/analytics/v3/management/accounts/xxxx/webproperties"}}]}'
2
+
@@ -0,0 +1 @@
1
+ PERMISSION_ERROR = '{"error":{"errors":[{"domain":"global","reason":"insufficientPermissions","message":"Insufficient Permission"}],"code":403,"message":"Insufficient Permission"}}'
metadata ADDED
@@ -0,0 +1,228 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: analytics-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.10
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alexis Montagne
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-12-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: multi_json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: oauth2
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: oauth
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: mocha
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: vcr
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: fakeweb
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ description: A Ruby interface to the Google Analytics API
143
+ email:
144
+ - alexis.montagne@gmail.com
145
+ executables: []
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - .coveralls.yml
150
+ - .gitignore
151
+ - Gemfile
152
+ - LICENSE
153
+ - README.md
154
+ - Rakefile
155
+ - analytics-rb.gemspec
156
+ - fixtures/vcr_cassettes/assets_up-fluence.yml
157
+ - fixtures/vcr_cassettes/assets_up-fluence/management/accounts.yml
158
+ - lib/analytics-rb.rb
159
+ - lib/analytics.rb
160
+ - lib/analytics/account.rb
161
+ - lib/analytics/client.rb
162
+ - lib/analytics/configurable.rb
163
+ - lib/analytics/core_ext/string.rb
164
+ - lib/analytics/error.rb
165
+ - lib/analytics/error/no_access_token_provided.rb
166
+ - lib/analytics/error/permission_insufficient.rb
167
+ - lib/analytics/oauth.rb
168
+ - lib/analytics/oauth2.rb
169
+ - lib/analytics/profile.rb
170
+ - lib/analytics/renderer/hashed.rb
171
+ - lib/analytics/renderer/plain.rb
172
+ - lib/analytics/report.rb
173
+ - lib/analytics/request.rb
174
+ - lib/analytics/version.rb
175
+ - lib/analytics/web_property.rb
176
+ - spec/analytics/account_spec.rb
177
+ - spec/analytics/client_spec.rb
178
+ - spec/analytics/core_ext/string_spec.rb
179
+ - spec/analytics/profile_spec.rb
180
+ - spec/analytics/renderer/hashed_spec.rb
181
+ - spec/analytics/report_spec.rb
182
+ - spec/analytics/request_spec.rb
183
+ - spec/analytics/web_propery_spec.rb
184
+ - spec/analytics_spec.rb
185
+ - spec/spec_helper.rb
186
+ - spec/support/management_profiles.rb
187
+ - spec/support/management_web_properties.rb
188
+ - spec/support/manangement_accounts.rb
189
+ - spec/support/permission_error.rb
190
+ homepage: http://www.github.com/AlexisMontagne/analytics
191
+ licenses: []
192
+ post_install_message:
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ none: false
198
+ requirements:
199
+ - - ! '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ required_rubygems_version: !ruby/object:Gem::Requirement
203
+ none: false
204
+ requirements:
205
+ - - ! '>='
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ requirements: []
209
+ rubyforge_project:
210
+ rubygems_version: 1.8.23
211
+ signing_key:
212
+ specification_version: 3
213
+ summary: Basic implementation of an ruby interface to the Analytics API
214
+ test_files:
215
+ - spec/analytics/account_spec.rb
216
+ - spec/analytics/client_spec.rb
217
+ - spec/analytics/core_ext/string_spec.rb
218
+ - spec/analytics/profile_spec.rb
219
+ - spec/analytics/renderer/hashed_spec.rb
220
+ - spec/analytics/report_spec.rb
221
+ - spec/analytics/request_spec.rb
222
+ - spec/analytics/web_propery_spec.rb
223
+ - spec/analytics_spec.rb
224
+ - spec/spec_helper.rb
225
+ - spec/support/management_profiles.rb
226
+ - spec/support/management_web_properties.rb
227
+ - spec/support/manangement_accounts.rb
228
+ - spec/support/permission_error.rb