analytics-rb 0.0.10

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