ariadna 1.0.0
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.
- 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
|