garb 0.2.1
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/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
|