ariadna 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +138 -0
- data/Rakefile +2 -0
- data/ariadna.gemspec +20 -0
- data/lib/ariadna/account.rb +45 -0
- data/lib/ariadna/analytics.rb +11 -0
- data/lib/ariadna/connexion.rb +118 -0
- data/lib/ariadna/delegator.rb +20 -0
- data/lib/ariadna/error.rb +11 -0
- data/lib/ariadna/error_code.rb +22 -0
- data/lib/ariadna/profile.rb +42 -0
- data/lib/ariadna/result.rb +167 -0
- data/lib/ariadna/version.rb +3 -0
- data/lib/ariadna/web_property.rb +40 -0
- data/lib/ariadna.rb +16 -0
- data/spec/fixtures/accounts.yml +13 -0
- data/spec/fixtures/errors.yml +11 -0
- data/spec/fixtures/profiles.yml +45 -0
- data/spec/fixtures/results.yml +51 -0
- data/spec/fixtures/webProperties.yml +16 -0
- data/spec/lib/ariadna/account_spec.rb +46 -0
- data/spec/lib/ariadna/analytics_spec.rb +25 -0
- data/spec/lib/ariadna/error_code_spec.rb +32 -0
- data/spec/lib/ariadna/error_spec.rb +48 -0
- data/spec/lib/ariadna/profile_spec.rb +67 -0
- data/spec/lib/ariadna/result_spec.rb +84 -0
- data/spec/lib/ariadna/web_property_spec.rb +56 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/fake_connector.rb +55 -0
- metadata +141 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
13
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
+
watch('config/routes.rb') { "spec/routing" }
|
15
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
+
# Capybara request specs
|
17
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
18
|
+
end
|
19
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jorge Alvarez
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# Ariadna
|
2
|
+
|
3
|
+
Google Analytics API wrapper.
|
4
|
+
|
5
|
+
It uses Oauth2 as authorization
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'ariadna'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ariadna
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Create a new connexion with your Oauth2 access token
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
analytics = Ariadna::Analytics.new(access_token)
|
27
|
+
```
|
28
|
+
|
29
|
+
Get a list of all accounts available
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
accounts = analytics.accounts
|
33
|
+
```
|
34
|
+
|
35
|
+
Get a list of all web properties available for an account
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
properties = accounts.first.properties.all
|
39
|
+
```
|
40
|
+
|
41
|
+
Get a list of all profiles available for a web property
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
profiles = properties.first.profiles.all
|
45
|
+
```
|
46
|
+
|
47
|
+
Create a query with your metrics and dimensions
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
results = profile.results.select(
|
51
|
+
:metrics => [:visits, :bounces, :timeOnSite],
|
52
|
+
:dimensions => [:country]
|
53
|
+
)
|
54
|
+
.where(
|
55
|
+
:start_date => Date.today,
|
56
|
+
:end_date => 2.months.ago,
|
57
|
+
:browser => "==Firefox"
|
58
|
+
)
|
59
|
+
.limit(100)
|
60
|
+
.offset(40)
|
61
|
+
.order([:visits, :bounces])
|
62
|
+
.all
|
63
|
+
```
|
64
|
+
|
65
|
+
All the metrics and dimensions returned by the query are mapped into attributes.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
@results.each do |result|
|
69
|
+
puts result.visits
|
70
|
+
puts result.bounces
|
71
|
+
puts result.timeonsite
|
72
|
+
puts result.country
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
### Create a connexion
|
77
|
+
|
78
|
+
Ariadna::Analytics.new(access_token, proxy_settings, refresh_token_data)
|
79
|
+
|
80
|
+
There are three possible params:
|
81
|
+
|
82
|
+
access_token (mandatory): an oauth2 access token
|
83
|
+
|
84
|
+
proxy_settings (optional): a hash containing your proxy options
|
85
|
+
|
86
|
+
refresh_token_data (optional): a hash with information about your app so access_token can be renewed automatically in case it is expired.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
analytics = Ariadna::Analytics.new(
|
90
|
+
access_token,
|
91
|
+
{ proxy_host: 'proxy.yourproxy.com',
|
92
|
+
proxy_port: 8080,
|
93
|
+
proxy_user: 'username',
|
94
|
+
proxy_pass: 'password'
|
95
|
+
},
|
96
|
+
# Google access tokens are short term so chances are you are going to need to refresh them
|
97
|
+
{ refresh_token: analytics_refresh_token,
|
98
|
+
client_id: 'apps.googleusercontent.com',
|
99
|
+
client_secret: 'client_secret',
|
100
|
+
current_user: current_user
|
101
|
+
}
|
102
|
+
)
|
103
|
+
```
|
104
|
+
|
105
|
+
### Access token
|
106
|
+
|
107
|
+
Ariadna is agnostic about the way you get your Oauth2 access token.
|
108
|
+
|
109
|
+
For the development of this gem I've been using [Omiauth](https://github.com/intridea/omniauth) with the [Google Oauth2 strategy](https://github.com/zquestz/omniauth-google-oauth2)
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
gem 'omniauth'
|
113
|
+
|
114
|
+
gem 'omniauth-google-oauth2'
|
115
|
+
```
|
116
|
+
|
117
|
+
Google Oauth2 tokens have a very short life. To make things easy if the connexion gives a 401 error and there is a refresh token passed as a param Ariadna will try to get a new access token from Google and store it in the curren user info calling update_access_token_from_google. If you want to use this feature you must create a method in your user model that saves this token.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
def update_access_token_from_google(new_token)
|
121
|
+
update_attribute(:google_oauth2_token, new_token)
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
It is obviously out of the scope of this gem to update tokens but it is definetly something that will make your life easier.
|
126
|
+
|
127
|
+
|
128
|
+
## Contributing
|
129
|
+
|
130
|
+
1. Fork it
|
131
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
132
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
133
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
134
|
+
5. Create new Pull Request
|
135
|
+
|
136
|
+
## Contributors
|
137
|
+
|
138
|
+
* Jorge Alvarez [http://www.alvareznavarro.es](http://www.alvareznavarro.es/?utm_source=github&utm_medium=gem&utm_campaign=ariadna)
|
data/Rakefile
ADDED
data/ariadna.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/ariadna/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Jorge Alvarez"]
|
6
|
+
gem.email = ["jorge@alvareznavarro.es"]
|
7
|
+
gem.description = %q{Google Analytics A.P.I. V3 wrapper with oauth2}
|
8
|
+
gem.summary = %q{Google Analytics A.P.I. V3 wrapper with oauth2}
|
9
|
+
gem.homepage = "https://github.com/jorgegorka/ariadna"
|
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 = "ariadna"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Ariadna::VERSION
|
17
|
+
gem.add_development_dependency "pry"
|
18
|
+
gem.add_development_dependency "rspec"
|
19
|
+
gem.add_development_dependency "guard-rspec"
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Ariadna
|
2
|
+
class Account
|
3
|
+
|
4
|
+
class << self;
|
5
|
+
attr_reader :url
|
6
|
+
attr_accessor :owner
|
7
|
+
end
|
8
|
+
|
9
|
+
#attr_reader :id, :link, :name, :properties_url
|
10
|
+
|
11
|
+
@url = "https://www.googleapis.com/analytics/v3/management/accounts"
|
12
|
+
|
13
|
+
def initialize(item)
|
14
|
+
item.each do |k,v|
|
15
|
+
instance_variable_set("@#{k}", v)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.all
|
20
|
+
@accounts ||= create_accounts
|
21
|
+
end
|
22
|
+
|
23
|
+
def properties
|
24
|
+
Delegator.new(WebProperty, self)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.create_accounts
|
30
|
+
accounts = Ariadna.connexion.get_url(self.url)
|
31
|
+
if (accounts["totalResults"].to_i > 0)
|
32
|
+
create_attributes(accounts["items"])
|
33
|
+
accounts["items"].map do |account|
|
34
|
+
Account.new(account)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.create_attributes(items)
|
40
|
+
items.first.each do |k,v|
|
41
|
+
attr_reader k.to_sym
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Ariadna
|
4
|
+
class Connexion
|
5
|
+
|
6
|
+
def initialize(token, proxy_options, refresh_info)
|
7
|
+
@token = token
|
8
|
+
extract_proxy_options(proxy_options) if proxy_options
|
9
|
+
extract_refresh_info(refresh_info) if refresh_info
|
10
|
+
end
|
11
|
+
|
12
|
+
# get url and try to refresh token if Unauthorized
|
13
|
+
def get_url(url, params=nil)
|
14
|
+
uri = URI(url)
|
15
|
+
headers = Hash.new
|
16
|
+
resp = get_conn(uri)
|
17
|
+
|
18
|
+
case resp
|
19
|
+
# response is ok
|
20
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
21
|
+
parse_response(resp)
|
22
|
+
# if we have a refresh token we can ask for a new access token
|
23
|
+
when Net::HTTPUnauthorized
|
24
|
+
get_url(url) if @refresh_token and get_access_token
|
25
|
+
when Net::HTTPBadRequest
|
26
|
+
raise Error.new(parse_response(resp))
|
27
|
+
when Net::HTTPNotFound
|
28
|
+
["not found", uri]
|
29
|
+
else
|
30
|
+
resp.value
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def extract_proxy_options(proxy_options)
|
38
|
+
return unless proxy_options.present?
|
39
|
+
@use_proxy = true
|
40
|
+
@proxy_host = proxy_options[:proxy_host]
|
41
|
+
@proxy_port = proxy_options[:proxy_port]
|
42
|
+
@proxy_user = proxy_options[:proxy_user]
|
43
|
+
@proxy_pass = proxy_options[:proxy_pass]
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_refresh_info(refresh_info)
|
47
|
+
return unless refresh_info.present?
|
48
|
+
@refresh_token = refresh_info[:refresh_token]
|
49
|
+
@client_id = refresh_info[:client_id]
|
50
|
+
@client_secret = refresh_info[:client_secret]
|
51
|
+
@current_user = refresh_info[:current_user]
|
52
|
+
end
|
53
|
+
|
54
|
+
# refresh access token as google access tokens have short term live
|
55
|
+
def get_access_token
|
56
|
+
uri = URI("https://accounts.google.com/o/oauth2/token")
|
57
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
58
|
+
req.set_form_data(
|
59
|
+
'client_id' => @client_id,
|
60
|
+
'client_secret' => @client_secret,
|
61
|
+
'refresh_token' => @refresh_token,
|
62
|
+
'grant_type' => 'refresh_token'
|
63
|
+
)
|
64
|
+
if @use_proxy
|
65
|
+
conn = Net::HTTP::Proxy(@proxy_host, @proxy_port, @proxy_user, @proxy_pass).start(uri.hostname, uri.port) {|http|
|
66
|
+
http.request(req)
|
67
|
+
}
|
68
|
+
else
|
69
|
+
# conn = Net::HTTP.new(uri.host, uri.port)
|
70
|
+
# conn.request(req)
|
71
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
72
|
+
http.use_ssl = uri.port == 443
|
73
|
+
conn = http.start { |http| http.request(req) }
|
74
|
+
conn
|
75
|
+
end
|
76
|
+
|
77
|
+
case conn
|
78
|
+
# response is ok
|
79
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
80
|
+
# use the new access token
|
81
|
+
refresh_info = parse_response(conn)
|
82
|
+
@token = refresh_info["access_token"]
|
83
|
+
@current_user.update_access_token_from_google(@token)
|
84
|
+
return true
|
85
|
+
# if not allowed revoke access
|
86
|
+
when Net::HTTPUnauthorized
|
87
|
+
@refresh_token = nil
|
88
|
+
else
|
89
|
+
conn
|
90
|
+
end
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_conn(uri)
|
95
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
96
|
+
req["Authorization"] = "Bearer #{@token}"
|
97
|
+
if @use_proxy
|
98
|
+
res = Net::HTTP::Proxy(@proxy_host, @proxy_port, @proxy_user, @proxy_pass).start(uri.hostname, uri.port) {|http|
|
99
|
+
http.request(req)
|
100
|
+
}
|
101
|
+
res
|
102
|
+
else
|
103
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
104
|
+
http.use_ssl = uri.port == 443
|
105
|
+
conn = http.start {|http| http.request(req) }
|
106
|
+
conn
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_response(resp)
|
111
|
+
JSON.parse resp.body
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_params(params)
|
115
|
+
{}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ariadna
|
2
|
+
class Delegator < BasicObject
|
3
|
+
|
4
|
+
def initialize(target, owner)
|
5
|
+
@target = target
|
6
|
+
@target.owner = owner
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def method_missing(name, *args, &block)
|
12
|
+
target.send(name, *args, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def target
|
16
|
+
@target ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Ariadna
|
2
|
+
class Error < StandardError
|
3
|
+
attr_reader :errors, :code, :message
|
4
|
+
|
5
|
+
def initialize(error_codes)
|
6
|
+
@errors = ErrorCode.get_errors(error_codes["error"]["errors"])
|
7
|
+
@code = error_codes["error"]["code"]
|
8
|
+
@message = error_codes["error"]["message"]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ariadna
|
2
|
+
class ErrorCode
|
3
|
+
def initialize(item)
|
4
|
+
item.each do |k,v|
|
5
|
+
instance_variable_set("@#{k}", v)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.get_errors(errors)
|
10
|
+
create_attributes(errors.first)
|
11
|
+
errors.map do |item|
|
12
|
+
ErrorCode.new(item)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create_attributes(item)
|
17
|
+
item.each do |k,v|
|
18
|
+
attr_reader k.to_sym
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Ariadna
|
2
|
+
class Profile
|
3
|
+
|
4
|
+
class << self;
|
5
|
+
attr_accessor :owner
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :id, :link, :name, :goals, :parent
|
9
|
+
|
10
|
+
def initialize(item)
|
11
|
+
item.each do |k,v|
|
12
|
+
instance_variable_set("@#{k}", v)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.all
|
17
|
+
@profiles ||= create_profiles
|
18
|
+
end
|
19
|
+
|
20
|
+
def results
|
21
|
+
Delegator.new(Result, self)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.create_profiles
|
27
|
+
profiles = Ariadna.connexion.get_url(@owner.childLink["href"])
|
28
|
+
if (profiles["totalResults"].to_i > 0)
|
29
|
+
create_attributes(profiles["items"])
|
30
|
+
profiles["items"].map do |item|
|
31
|
+
Profile.new(item)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.create_attributes(items)
|
37
|
+
items.first.each do |k,v|
|
38
|
+
attr_reader k.to_sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Ariadna
|
2
|
+
class Result
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :owner
|
6
|
+
attr_accessor :url
|
7
|
+
end
|
8
|
+
|
9
|
+
URL = "https://www.googleapis.com/analytics/v3/data/ga"
|
10
|
+
|
11
|
+
#gel all results
|
12
|
+
def self.all
|
13
|
+
get_results
|
14
|
+
end
|
15
|
+
|
16
|
+
# metrics and dimensions
|
17
|
+
|
18
|
+
def self.select(params)
|
19
|
+
get_metrics_and_dimensions(params)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# main filter conditions
|
24
|
+
def self.where(params)
|
25
|
+
extract_dates(params)
|
26
|
+
get_filters(params) unless params.empty?
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# sort conditions for the query
|
31
|
+
def self.order(params)
|
32
|
+
conditions.merge!({"sort" => api_compatible_names(params)})
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# number of results returned
|
37
|
+
def self.limit(results)
|
38
|
+
if ((results.to_i > 0) and (results.to_i < 1001))
|
39
|
+
conditions.merge!({"max-results" => results.to_i})
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# number of row from which to start collecting results (used for pagination)
|
45
|
+
def self.offset(offset)
|
46
|
+
if (offset.to_i > 0)
|
47
|
+
conditions.merge!({"start-index" => offset.to_i})
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# lazy load query. Only executed when actually needed
|
53
|
+
def self.each(&block)
|
54
|
+
get_results.each(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def self.conditions
|
60
|
+
@conditions ||= {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.accessor_name(header)
|
64
|
+
header["name"].sub('ga:', '')
|
65
|
+
end
|
66
|
+
|
67
|
+
# create attributes for each metric and dimension
|
68
|
+
def self.create_attributes(results)
|
69
|
+
summary_rows = Hash.new
|
70
|
+
summary_rows.merge!(results)
|
71
|
+
summary_rows.delete("columnHeaders")
|
72
|
+
summary_rows.delete("rows")
|
73
|
+
summary_rows.each do |row, value|
|
74
|
+
attr_reader row.to_sym
|
75
|
+
end
|
76
|
+
summary_rows
|
77
|
+
end
|
78
|
+
|
79
|
+
# create attributes for each metric and dimension
|
80
|
+
def self.create_metrics_and_dimensions(headers)
|
81
|
+
headers.each do |header|
|
82
|
+
attr_reader accessor_name(header).to_sym
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# map the json results collection into result objects
|
87
|
+
# every metric and dimension is created as an attribute
|
88
|
+
# I.E. You can get result.visits or result.bounces
|
89
|
+
def self.get_results
|
90
|
+
self.url = generate_url
|
91
|
+
results = Ariadna.connexion.get_url(self.url)
|
92
|
+
|
93
|
+
return results unless results.is_a? Hash
|
94
|
+
|
95
|
+
if (results["totalResults"].to_i > 0)
|
96
|
+
#create an accessor for each summary attribute
|
97
|
+
summary_rows = create_attributes(results)
|
98
|
+
#create an accessor for each metric and dimension
|
99
|
+
create_metrics_and_dimensions(results["columnHeaders"])
|
100
|
+
results["rows"].map do |items|
|
101
|
+
res = Result.new
|
102
|
+
#assign values to summary fields
|
103
|
+
summary_rows.each do |name, value|
|
104
|
+
res.instance_variable_set("@#{name}", value)
|
105
|
+
end
|
106
|
+
#assign values to metrics and dimensions
|
107
|
+
items.each do |item|
|
108
|
+
res.instance_variable_set("@#{accessor_name(results["columnHeaders"][(items.index(item))])}", set_value_for_result(results["columnHeaders"][(items.index(item))], item))
|
109
|
+
end
|
110
|
+
res
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.set_value_for_result(header, item)
|
116
|
+
case header["dataType"]
|
117
|
+
when "INTEGER"
|
118
|
+
return item.to_i
|
119
|
+
when "CURRENCY"
|
120
|
+
return item.to_d
|
121
|
+
when "FLOAT"
|
122
|
+
return item.to_f
|
123
|
+
when "TIME"
|
124
|
+
Time.at(item.to_d).gmtime.strftime('%R:%S')
|
125
|
+
else
|
126
|
+
return item.to_s
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.generate_url
|
131
|
+
params = conditions.merge({"ids" => "ga:#{@owner.id}"})
|
132
|
+
"#{URL}?" + params.map{ |k,v| "#{k}=#{v}"}.join("&")
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.get_filters(params)
|
136
|
+
filters = params.map do |k,v|
|
137
|
+
"#{api_compatible_names([k])}#{url_encoded_value(v)}"
|
138
|
+
end
|
139
|
+
conditions.merge!({"filters" => filters.join(",")})
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.get_metrics_and_dimensions(params)
|
143
|
+
params.each do |k,v|
|
144
|
+
conditions.merge!({"#{k}" => api_compatible_names(v)})
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.api_compatible_names(values)
|
149
|
+
values.collect {|e| "ga:#{e}"}.join(",")
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.extract_dates(params)
|
153
|
+
start_date = params.delete(:start_date)
|
154
|
+
end_date = params.delete(:end_date)
|
155
|
+
conditions.merge!({"start-date" => format_date(start_date)})
|
156
|
+
conditions.merge!({"end-date" => format_date(end_date)})
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.format_date(date)
|
160
|
+
date.strftime("%Y-%m-%d")
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.url_encoded_value(value)
|
164
|
+
URI.escape(value, "=@!><")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|