garb 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +178 -0
- data/Rakefile +54 -0
- data/lib/extensions/happymapper.rb +67 -0
- data/lib/extensions/operator.rb +20 -0
- data/lib/extensions/string.rb +13 -0
- data/lib/extensions/symbol.rb +36 -0
- data/lib/garb.rb +70 -0
- data/lib/garb/authentication_request.rb +46 -0
- data/lib/garb/data_request.rb +28 -0
- data/lib/garb/oauth_session.rb +21 -0
- data/lib/garb/profile.rb +45 -0
- data/lib/garb/report.rb +31 -0
- data/lib/garb/report_parameter.rb +36 -0
- data/lib/garb/report_response.rb +62 -0
- data/lib/garb/resource.rb +89 -0
- data/lib/garb/session.rb +19 -0
- data/lib/garb/version.rb +13 -0
- data/test/fixtures/profile_feed.xml +33 -0
- data/test/fixtures/report_feed.xml +46 -0
- data/test/test_helper.rb +16 -0
- data/test/unit/authentication_request_test.rb +91 -0
- data/test/unit/data_request_test.rb +52 -0
- data/test/unit/garb_test.rb +9 -0
- data/test/unit/oauth_session_test.rb +11 -0
- data/test/unit/operator_test.rb +37 -0
- data/test/unit/profile_test.rb +58 -0
- data/test/unit/report_parameter_test.rb +62 -0
- data/test/unit/report_response_test.rb +29 -0
- data/test/unit/report_test.rb +71 -0
- data/test/unit/resource_test.rb +19 -0
- data/test/unit/session_test.rb +26 -0
- data/test/unit/string_test.rb +9 -0
- data/test/unit/symbol_test.rb +44 -0
- metadata +99 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Garb
|
2
|
+
class DataRequest
|
3
|
+
|
4
|
+
def initialize(base_url, parameters={})
|
5
|
+
@base_url = base_url
|
6
|
+
@parameters = parameters
|
7
|
+
end
|
8
|
+
|
9
|
+
def query_string
|
10
|
+
parameter_list = @parameters.map {|k,v| "#{k}=#{v}" }
|
11
|
+
parameter_list.empty? ? '' : "?#{parameter_list.join('&')}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def uri
|
15
|
+
URI.parse(@base_url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_request
|
19
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
20
|
+
http.use_ssl = true
|
21
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
22
|
+
response = http.get("#{uri.path}#{query_string}", 'Authorization' => "GoogleLogin auth=#{Session.auth_token}")
|
23
|
+
raise response.body.inspect unless response.is_a?(Net::HTTPOK)
|
24
|
+
response
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Garb
|
2
|
+
class OAuthSession
|
3
|
+
attr_accessor :access_token
|
4
|
+
|
5
|
+
OAuthGetRequestToken = "https://www.google.com/accounts/OAuthGetRequestToken"
|
6
|
+
OAuthAuthorizeToken = "https://www.google.com/accounts/OAuthAuthorizeToken"
|
7
|
+
OAuthGetAccessToken = "https://www.google.com/accounts/OAuthGetAccessToken"
|
8
|
+
|
9
|
+
def get_request_token
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def authorization_request
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_access_token
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/garb/profile.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Garb
|
2
|
+
class Profile
|
3
|
+
|
4
|
+
attr_reader :table_id, :title, :account_name
|
5
|
+
|
6
|
+
class Property
|
7
|
+
include HappyMapper
|
8
|
+
|
9
|
+
tag 'property'
|
10
|
+
namespace 'dxp'
|
11
|
+
|
12
|
+
attribute :name, String
|
13
|
+
attribute :value, String
|
14
|
+
end
|
15
|
+
|
16
|
+
class Entry
|
17
|
+
include HappyMapper
|
18
|
+
|
19
|
+
tag 'entry'
|
20
|
+
|
21
|
+
element :id, Integer
|
22
|
+
element :title, String
|
23
|
+
element :tableId, String, :namespace => 'dxp'
|
24
|
+
|
25
|
+
# has_one :table_id, TableId
|
26
|
+
has_many :properties, Property
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(entry)
|
30
|
+
@title = entry.title
|
31
|
+
@table_id = entry.tableId
|
32
|
+
@account_name = entry.properties.detect{|p| p.name == 'ga:accountName'}.value
|
33
|
+
end
|
34
|
+
|
35
|
+
def id
|
36
|
+
@table_id.sub(/^ga:/, '')
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.all
|
40
|
+
url = "https://www.google.com/analytics/feeds/accounts/#{Session.email}"
|
41
|
+
response = DataRequest.new(url).send_request
|
42
|
+
Entry.parse(response.body).map {|e| Garb::Profile.new(e)}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/garb/report.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Garb
|
2
|
+
class Report
|
3
|
+
include Resource::ResourceMethods
|
4
|
+
|
5
|
+
MONTH = 2592000
|
6
|
+
URL = "https://www.google.com/analytics/feeds/data"
|
7
|
+
|
8
|
+
def initialize(profile, opts={})
|
9
|
+
@profile = profile
|
10
|
+
|
11
|
+
@start_date = opts.fetch(:start_date, Time.now - MONTH)
|
12
|
+
@end_date = opts.fetch(:end_date, Time.now)
|
13
|
+
@limit = opts.fetch(:limit, nil)
|
14
|
+
@offset = opts.fetch(:offset, nil)
|
15
|
+
|
16
|
+
# clear filters and sort
|
17
|
+
@filters = ReportParameter.new(:filters)
|
18
|
+
@sorts = ReportParameter.new(:sort)
|
19
|
+
|
20
|
+
metrics opts.fetch(:metrics, [])
|
21
|
+
dimensions opts.fetch(:dimensions, [])
|
22
|
+
filter opts.fetch(:filter, [])
|
23
|
+
sort opts.fetch(:sort, [])
|
24
|
+
end
|
25
|
+
|
26
|
+
def results
|
27
|
+
ReportResponse.new(send_request_for_body).results
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Garb
|
2
|
+
class ReportParameter
|
3
|
+
|
4
|
+
attr_reader :elements
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@elements = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(element)
|
16
|
+
(@elements += [element].flatten).compact!
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_params
|
21
|
+
params = self.elements.map do |elem|
|
22
|
+
case elem
|
23
|
+
when Hash
|
24
|
+
elem.collect do |k,v|
|
25
|
+
next unless k.is_a?(Operator)
|
26
|
+
"#{k.target}#{URI.encode(k.operator.to_s, /[=<>]/)}#{CGI::escape(v.to_s)}"
|
27
|
+
end.join(';')
|
28
|
+
else
|
29
|
+
elem.to_ga
|
30
|
+
end
|
31
|
+
end.join(',')
|
32
|
+
|
33
|
+
params.empty? ? {} : {self.name => params}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Garb
|
2
|
+
class ReportResponse
|
3
|
+
# include Enumerable
|
4
|
+
|
5
|
+
def initialize(response_body)
|
6
|
+
@xml = response_body
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
entries = Entry.parse(@xml)
|
11
|
+
|
12
|
+
@results = entries.collect do |entry|
|
13
|
+
hash = {}
|
14
|
+
|
15
|
+
entry.metrics.each do |m|
|
16
|
+
name = m.name.sub(/^ga\:/,'').underscored
|
17
|
+
hash.merge!({name => m.value})
|
18
|
+
end
|
19
|
+
|
20
|
+
entry.dimensions.each do |d|
|
21
|
+
name = d.name.sub(/^ga\:/,'').underscored
|
22
|
+
hash.merge!({name => d.value})
|
23
|
+
end
|
24
|
+
|
25
|
+
OpenStruct.new(hash)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def results
|
30
|
+
@results || parse
|
31
|
+
end
|
32
|
+
|
33
|
+
class Metric
|
34
|
+
include HappyMapper
|
35
|
+
|
36
|
+
tag 'metric'
|
37
|
+
namespace 'dxp'
|
38
|
+
|
39
|
+
attribute :name, String
|
40
|
+
attribute :value, String
|
41
|
+
end
|
42
|
+
|
43
|
+
class Dimension
|
44
|
+
include HappyMapper
|
45
|
+
|
46
|
+
tag 'dimension'
|
47
|
+
namespace 'dxp'
|
48
|
+
|
49
|
+
attribute :name, String
|
50
|
+
attribute :value, String
|
51
|
+
end
|
52
|
+
|
53
|
+
class Entry
|
54
|
+
include HappyMapper
|
55
|
+
|
56
|
+
tag 'entry'
|
57
|
+
|
58
|
+
has_many :metrics, Metric
|
59
|
+
has_many :dimensions, Dimension
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Garb
|
2
|
+
module Resource
|
3
|
+
MONTH = 2592000
|
4
|
+
URL = "https://www.google.com/analytics/feeds/data"
|
5
|
+
|
6
|
+
def self.included(report)
|
7
|
+
report.extend(ResourceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ResourceMethods
|
11
|
+
|
12
|
+
def metrics(*fields)
|
13
|
+
@metrics ||= ReportParameter.new(:metrics)
|
14
|
+
@metrics << fields
|
15
|
+
end
|
16
|
+
|
17
|
+
def dimensions(*fields)
|
18
|
+
@dimensions ||= ReportParameter.new(:dimensions)
|
19
|
+
@dimensions << fields
|
20
|
+
end
|
21
|
+
|
22
|
+
def filter(*hash)
|
23
|
+
@filters << hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def filters
|
27
|
+
@filters ||= ReportParameter.new(:filters)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sort(*fields)
|
31
|
+
@sorts << fields
|
32
|
+
end
|
33
|
+
|
34
|
+
def sorts
|
35
|
+
@sorts ||= ReportParameter.new(:sort)
|
36
|
+
end
|
37
|
+
|
38
|
+
def results(profile, opts = {}, &block)
|
39
|
+
@profile = profile
|
40
|
+
|
41
|
+
# clear filters and sort
|
42
|
+
@filters = ReportParameter.new(:filters)
|
43
|
+
@sorts = ReportParameter.new(:sort)
|
44
|
+
|
45
|
+
@start_date = opts.fetch(:start_date, Time.now - MONTH)
|
46
|
+
@end_date = opts.fetch(:end_date, Time.now)
|
47
|
+
@limit = opts.fetch(:limit, nil)
|
48
|
+
@offset = opts.fetch(:offset, nil)
|
49
|
+
|
50
|
+
instance_eval(&block) if block_given?
|
51
|
+
|
52
|
+
ReportResponse.new(send_request_for_body).results
|
53
|
+
end
|
54
|
+
|
55
|
+
def page_params
|
56
|
+
{'max-results' => @limit, 'start-index' => @offset}.reject{|k,v| v.nil?}
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_params
|
60
|
+
{'ids' => @profile.table_id,
|
61
|
+
'start-date' => format_time(@start_date),
|
62
|
+
'end-date' => format_time(@end_date)}
|
63
|
+
end
|
64
|
+
|
65
|
+
def params
|
66
|
+
[
|
67
|
+
metrics.to_params,
|
68
|
+
dimensions.to_params,
|
69
|
+
sorts.to_params,
|
70
|
+
filters.to_params,
|
71
|
+
page_params
|
72
|
+
].inject(default_params) do |p, i|
|
73
|
+
p.merge(i)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def format_time(t)
|
78
|
+
t.strftime('%Y-%m-%d')
|
79
|
+
end
|
80
|
+
|
81
|
+
def send_request_for_body
|
82
|
+
request = DataRequest.new(URL, params)
|
83
|
+
response = request.send_request
|
84
|
+
response.body
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/garb/session.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Garb
|
2
|
+
class Session
|
3
|
+
|
4
|
+
def self.login(email, password)
|
5
|
+
@email = email
|
6
|
+
auth_request = AuthenticationRequest.new(email, password)
|
7
|
+
@auth_token = auth_request.auth_token
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.auth_token
|
11
|
+
@auth_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.email
|
15
|
+
@email
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/garb/version.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
2
|
+
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:dxp='http://schemas.google.com/analytics/2009'>
|
3
|
+
<id>http://www.google.com/analytics/feeds/accounts/email@example.com</id>
|
4
|
+
<updated>2009-03-27T08:14:28.000-07:00</updated>
|
5
|
+
<title type='text'>Profile list for email@example.com</title>
|
6
|
+
<link rel='self' type='application/atom+xml' href='http://www.google.com/analytics/feeds/accounts/email@example.com'/>
|
7
|
+
<author><name>Google Analytics</name></author>
|
8
|
+
<generator version='1.0'>Google Analytics</generator>
|
9
|
+
<openSearch:totalResults>2</openSearch:totalResults>
|
10
|
+
<openSearch:startIndex>1</openSearch:startIndex>
|
11
|
+
<openSearch:itemsPerPage>2</openSearch:itemsPerPage>
|
12
|
+
<entry>
|
13
|
+
<id>http://www.google.com/analytics/feeds/accounts/ga:12345</id>
|
14
|
+
<updated>2008-07-21T14:05:57.000-07:00</updated>
|
15
|
+
<title type='text'>Historical</title>
|
16
|
+
<dxp:tableId>ga:12345</dxp:tableId>
|
17
|
+
<dxp:property name='ga:accountId' value='1111'/>
|
18
|
+
<dxp:property name='ga:accountName' value='Blog Beta'/>
|
19
|
+
<dxp:property name='ga:profileId' value='1212'/>
|
20
|
+
<dxp:property name='ga:webPropertyId' value='UA-1111-1'/>
|
21
|
+
</entry>
|
22
|
+
<entry>
|
23
|
+
<id>http://www.google.com/analytics/feeds/accounts/ga:12346</id>
|
24
|
+
<updated>2008-11-24T11:51:07.000-08:00</updated>
|
25
|
+
<title type='text'>Presently</title>
|
26
|
+
<dxp:tableId>ga:12346</dxp:tableId>
|
27
|
+
<dxp:property name='ga:accountId' value='1111'/>
|
28
|
+
<dxp:property name='ga:accountName' value='Blog Beta'/>
|
29
|
+
<dxp:property name='ga:profileId' value='1213'/>
|
30
|
+
<dxp:property name='ga:webPropertyId' value='UA-1111-2'/>
|
31
|
+
</entry>
|
32
|
+
</feed>
|
33
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<feed xmlns='http://www.w3.org/2005/Atom'
|
3
|
+
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
|
4
|
+
xmlns:dxp='http://schemas.google.com/dataexport/2009'
|
5
|
+
xmlns:ga='http://schemas.google.com/analytics/2008'>
|
6
|
+
<id>http://www.google.com/analytics/feeds/data?ids=ga:983247&dimensions=ga:country,ga:city&metrics=ga:pageViews&start-date=2008-01-01&end-date=2008-01-02</id>
|
7
|
+
<updated>2008-01-02T15:59:59.999-08:00 </updated>
|
8
|
+
<title type="text">Google Analytics Data for Profile 983247</title>
|
9
|
+
<link href="http://www.google.com/analytics/feeds/data" rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml"/>
|
10
|
+
<link href="http://www.google.com/analytics/feeds/data?end-date=2008-01-02&start-date=2008-01-01&metrics=ga%3ApageViews&ids=ga%3A983247&dimensions=ga%3Acountry%2Cga%3Acity" rel="self" type="application/atom+xml"/>
|
11
|
+
<link href="http://www.google.com/analytics/feeds/data?start-index=1001&max-results=1000&end-date=2008-01-02&start-date=2008-01-01&metrics=ga%3ApageViews&ids=ga%3A983247&dimensions=ga%3Acountry%2Cga%3Acity" rel="next" type="application/atom+xml"/>
|
12
|
+
<author>
|
13
|
+
<name>Google Analytics</name>
|
14
|
+
</author>
|
15
|
+
<openSearch:startIndex>3</openSearch:startIndex>
|
16
|
+
<openSearch:itemsPerPage>4</openSearch:itemsPerPage>
|
17
|
+
<ga:webPropertyID>UA-983247-67</ga:webPropertyID>
|
18
|
+
<ga:start-date>2008-01-01</ga:start-date>
|
19
|
+
<ga:end-date>2008-01-02</ga:end-date>
|
20
|
+
|
21
|
+
<entry>
|
22
|
+
<id> http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=%28not%20set%29&ga:city=%28not%20set%29&start-date=2008-01-01&end-date=2008-01-02 </id>
|
23
|
+
<updated> 2008-01-01T16:00:00.001-08:00 </updated>
|
24
|
+
<title type="text"> ga:country=(not set) | ga:city=(not set) </title>
|
25
|
+
<link href="http://www.google.com/analytics/feeds/data" rel="self" type="application/atom+xml"/>
|
26
|
+
<dxp:dimension name="ga:country" value="(not set)" />
|
27
|
+
<dxp:dimension name="ga:city" value="(not set)" />
|
28
|
+
<dxp:metric name="ga:pageviews" value="33" />
|
29
|
+
</entry>
|
30
|
+
<entry>
|
31
|
+
<id> http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=Afghanistan&ga:city=Kabul&start-date=2008-01-01&end-date=2008-01-02 </id>
|
32
|
+
<updated> 2008-01-01T16:00:00.001-08:00 </updated>
|
33
|
+
<title type="text"> ga:country=Afghanistan | ga:city=Kabul </title>
|
34
|
+
<dxp:dimension name="ga:country" value="Afghanistan" />
|
35
|
+
<dxp:dimension name="ga:city" value="Kabul" />
|
36
|
+
<dxp:metric name="ga:pageviews" value="2" />
|
37
|
+
</entry>
|
38
|
+
<entry>
|
39
|
+
<id> http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=Albania&ga:city=Tirana&start-date=2008-01-01&end-date=2008-01-02 </id>
|
40
|
+
<updated> 2008-01-01T16:00:00.001-08:00 </updated>
|
41
|
+
<title type="text"> ga:country=Albania | ga:city=Tirana </title>
|
42
|
+
<dxp:dimension name="ga:country" value="Albania" />
|
43
|
+
<dxp:dimension name="ga:city" value="Tirana" />
|
44
|
+
<dxp:metric name="ga:pageviews" value="1" />
|
45
|
+
</entry>
|
46
|
+
</feed>
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.reject! { |e| e.include? 'TextMate' }
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'shoulda'
|
6
|
+
require 'mocha'
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/../lib/garb'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
|
12
|
+
def read_fixture(filename)
|
13
|
+
File.read(File.dirname(__FILE__) + "/fixtures/#{filename}")
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|