titanous-garb 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/README.md +262 -0
  2. data/Rakefile +56 -0
  3. data/lib/garb.rb +69 -0
  4. data/lib/garb/account.rb +21 -0
  5. data/lib/garb/account_feed_request.rb +25 -0
  6. data/lib/garb/authentication_request.rb +53 -0
  7. data/lib/garb/data_request.rb +42 -0
  8. data/lib/garb/destination.rb +20 -0
  9. data/lib/garb/filter_parameters.rb +41 -0
  10. data/lib/garb/goal.rb +20 -0
  11. data/lib/garb/management/account.rb +32 -0
  12. data/lib/garb/management/feed.rb +26 -0
  13. data/lib/garb/management/goal.rb +20 -0
  14. data/lib/garb/management/profile.rb +39 -0
  15. data/lib/garb/management/web_property.rb +30 -0
  16. data/lib/garb/model.rb +89 -0
  17. data/lib/garb/profile.rb +33 -0
  18. data/lib/garb/profile_reports.rb +16 -0
  19. data/lib/garb/report.rb +28 -0
  20. data/lib/garb/report_parameter.rb +25 -0
  21. data/lib/garb/report_response.rb +34 -0
  22. data/lib/garb/reports.rb +5 -0
  23. data/lib/garb/reports/bounces.rb +5 -0
  24. data/lib/garb/reports/exits.rb +5 -0
  25. data/lib/garb/reports/pageviews.rb +5 -0
  26. data/lib/garb/reports/unique_pageviews.rb +5 -0
  27. data/lib/garb/reports/visits.rb +5 -0
  28. data/lib/garb/resource.rb +115 -0
  29. data/lib/garb/session.rb +35 -0
  30. data/lib/garb/step.rb +13 -0
  31. data/lib/garb/version.rb +13 -0
  32. data/lib/support.rb +40 -0
  33. data/test/fixtures/cacert.pem +67 -0
  34. data/test/fixtures/profile_feed.xml +72 -0
  35. data/test/fixtures/report_feed.xml +46 -0
  36. data/test/test_helper.rb +37 -0
  37. data/test/unit/garb/account_feed_request_test.rb +9 -0
  38. data/test/unit/garb/account_test.rb +53 -0
  39. data/test/unit/garb/authentication_request_test.rb +121 -0
  40. data/test/unit/garb/data_request_test.rb +120 -0
  41. data/test/unit/garb/destination_test.rb +28 -0
  42. data/test/unit/garb/filter_parameters_test.rb +59 -0
  43. data/test/unit/garb/goal_test.rb +24 -0
  44. data/test/unit/garb/management/account_test.rb +54 -0
  45. data/test/unit/garb/management/profile_test.rb +59 -0
  46. data/test/unit/garb/management/web_property_test.rb +58 -0
  47. data/test/unit/garb/model_test.rb +134 -0
  48. data/test/unit/garb/oauth_session_test.rb +11 -0
  49. data/test/unit/garb/profile_reports_test.rb +29 -0
  50. data/test/unit/garb/profile_test.rb +77 -0
  51. data/test/unit/garb/report_parameter_test.rb +43 -0
  52. data/test/unit/garb/report_response_test.rb +37 -0
  53. data/test/unit/garb/report_test.rb +99 -0
  54. data/test/unit/garb/resource_test.rb +49 -0
  55. data/test/unit/garb/session_test.rb +91 -0
  56. data/test/unit/garb/step_test.rb +15 -0
  57. data/test/unit/garb_test.rb +26 -0
  58. data/test/unit/symbol_operator_test.rb +37 -0
  59. metadata +180 -0
@@ -0,0 +1,25 @@
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
+ value = self.elements.map{|param| Garb.to_google_analytics(param)}.join(',')
22
+ value.empty? ? {} : {self.name => value}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Garb
2
+ class ReportResponse
3
+ KEYS = ['dxp:metric', 'dxp:dimension']
4
+
5
+ def initialize(response_body, instance_klass = OpenStruct)
6
+ @xml = response_body
7
+ @instance_klass = instance_klass
8
+ end
9
+
10
+ def results
11
+ @results ||= parse
12
+ end
13
+
14
+ private
15
+ def parse
16
+ entries.map do |entry|
17
+ hash = values_for(entry).inject({}) do |h, v|
18
+ h.merge(Garb.from_ga(v['name']) => v['value'])
19
+ end
20
+
21
+ @instance_klass.new(hash)
22
+ end
23
+ end
24
+
25
+ def entries
26
+ entry_hash = Crack::XML.parse(@xml)
27
+ entry_hash ? [entry_hash['feed']['entry']].flatten.compact : []
28
+ end
29
+
30
+ def values_for(entry)
31
+ KEYS.map {|k| entry[k]}.flatten.compact
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ require 'garb/reports/exits'
2
+ require 'garb/reports/visits'
3
+ require 'garb/reports/bounces'
4
+ require 'garb/reports/pageviews'
5
+ require 'garb/reports/unique_pageviews'
@@ -0,0 +1,5 @@
1
+ class Bounces
2
+ extend Garb::Resource
3
+
4
+ metrics :bounces
5
+ end
@@ -0,0 +1,5 @@
1
+ class Exits
2
+ extend Garb::Resource
3
+
4
+ metrics :exits
5
+ end
@@ -0,0 +1,5 @@
1
+ class Pageviews
2
+ extend Garb::Resource
3
+
4
+ metrics :pageviews
5
+ end
@@ -0,0 +1,5 @@
1
+ class UniquePageviews
2
+ extend Garb::Resource
3
+
4
+ metrics :unique_pageviews
5
+ end
@@ -0,0 +1,5 @@
1
+ class Visits
2
+ extend Garb::Resource
3
+
4
+ metrics :visits
5
+ end
@@ -0,0 +1,115 @@
1
+ module Garb
2
+ module Resource
3
+ MONTH = 2592000
4
+ URL = "https://www.google.com/analytics/feeds/data"
5
+
6
+ def self.extended(base)
7
+ # define a method on a module that gets included into profile
8
+ # Exits would make:
9
+ # to enable profile.exits(options_hash, &block)
10
+ # returns Exits.results(self, options_hash, &block)
11
+ # every class defined which extends Resource will add to the module
12
+
13
+ # ActiveSupport::Deprecation.warn
14
+ ProfileReports.add_report_method(base)
15
+ end
16
+
17
+ %w(metrics dimensions sort).each do |parameter|
18
+ class_eval <<-CODE
19
+ def #{parameter}(*fields)
20
+ @#{parameter} ||= ReportParameter.new(:#{parameter})
21
+ @#{parameter} << fields
22
+ end
23
+
24
+ def clear_#{parameter}
25
+ @#{parameter} = ReportParameter.new(:#{parameter})
26
+ end
27
+ CODE
28
+ end
29
+
30
+ def filters(*hashes, &block)
31
+ @filter_parameters ||= FilterParameters.new
32
+
33
+ hashes.each do |hash|
34
+ @filter_parameters.parameters << hash
35
+ end
36
+
37
+ @filter_parameters.filters(&block) if block_given?
38
+ @filter_parameters
39
+ end
40
+
41
+ def clear_filters
42
+ @filter_parameters = FilterParameters.new
43
+ end
44
+
45
+ def set_segment_id(id)
46
+ @segment = "gaid::#{id.to_i}"
47
+ end
48
+
49
+ def segment
50
+ @segment
51
+ end
52
+
53
+ def set_instance_klass(klass)
54
+ @instance_klass = klass
55
+ end
56
+
57
+ def instance_klass
58
+ @instance_klass || OpenStruct
59
+ end
60
+
61
+ def results(profile, opts = {}, &block)
62
+ @profile = profile.is_a?(Profile) ? profile : Profile.first(profile, opts.fetch(:session, Session))
63
+
64
+ if @profile
65
+ @start_date = opts.fetch(:start_date, Time.now - MONTH)
66
+ @end_date = opts.fetch(:end_date, Time.now)
67
+ @limit = opts.fetch(:limit, nil)
68
+ @offset = opts.fetch(:offset, nil)
69
+
70
+ instance_eval(&block) if block_given?
71
+
72
+ ReportResponse.new(send_request_for_body, instance_klass).results
73
+ else
74
+ []
75
+ end
76
+ end
77
+
78
+ def page_params
79
+ {'max-results' => @limit, 'start-index' => @offset}.reject{|k,v| v.nil?}
80
+ end
81
+
82
+ def default_params
83
+ {'ids' => @profile.table_id,
84
+ 'start-date' => format_time(@start_date),
85
+ 'end-date' => format_time(@end_date)}
86
+ end
87
+
88
+ def segment_params
89
+ segment.nil? ? {} : {'segment' => segment}
90
+ end
91
+
92
+ def params
93
+ [
94
+ metrics.to_params,
95
+ dimensions.to_params,
96
+ sort.to_params,
97
+ filters.to_params,
98
+ page_params,
99
+ segment_params
100
+ ].inject(default_params) do |p, i|
101
+ p.merge(i)
102
+ end
103
+ end
104
+
105
+ def format_time(t)
106
+ t.strftime('%Y-%m-%d')
107
+ end
108
+
109
+ def send_request_for_body
110
+ request = DataRequest.new(@profile.session, URL, params)
111
+ response = request.send_request
112
+ response.body
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,35 @@
1
+ module Garb
2
+ class Session
3
+ module Methods
4
+ attr_accessor :auth_token, :access_token, :email
5
+
6
+ # use only for single user authentication
7
+ def login(email, password, opts={})
8
+ self.email = email
9
+ auth_request = AuthenticationRequest.new(email, password, opts)
10
+ self.auth_token = auth_request.auth_token(opts)
11
+ end
12
+
13
+ def single_user?
14
+ auth_token && auth_token.is_a?(String)
15
+ end
16
+
17
+ def oauth_user?
18
+ !access_token.nil?
19
+ end
20
+
21
+ def auth_sub(token)
22
+ self.auth_token = token
23
+ @auth_sub = true
24
+ end
25
+
26
+ def auth_sub?
27
+ @auth_sub
28
+ end
29
+
30
+ end
31
+
32
+ include Methods
33
+ extend Methods
34
+ end
35
+ end
data/lib/garb/step.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Garb
2
+ class Step
3
+ attr_reader :name, :number, :path
4
+
5
+ def initialize(attributes)
6
+ return unless attributes.is_a?(Hash)
7
+
8
+ @name = attributes['name']
9
+ @number = attributes['number'].to_i
10
+ @path = attributes['path']
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Garb
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 8
6
+ TINY = 5
7
+
8
+ def self.to_s # :nodoc:
9
+ [MAJOR, MINOR, TINY].join('.')
10
+ end
11
+
12
+ end
13
+ end
data/lib/support.rb ADDED
@@ -0,0 +1,40 @@
1
+ class SymbolOperator
2
+ def initialize(field, operator)
3
+ @field, @operator = field, operator
4
+ end unless method_defined?(:initialize)
5
+
6
+ def to_google_analytics
7
+ operators = {
8
+ :eql => '==',
9
+ :not_eql => '!=',
10
+ :gt => '>',
11
+ :gte => '>=',
12
+ :lt => '<',
13
+ :lte => '<=',
14
+ :matches => '==',
15
+ :does_not_match => '!=',
16
+ :contains => '=~',
17
+ :does_not_contain => '!~',
18
+ :substring => '=@',
19
+ :not_substring => '!@',
20
+ :desc => '-',
21
+ :descending => '-'
22
+ }
23
+
24
+ target = Garb.to_google_analytics(@field)
25
+ operator = operators[@operator]
26
+
27
+ [:desc, :descending].include?(@operator) ? "#{operator}#{target}" : "#{target}#{operator}"
28
+ end
29
+ end
30
+
31
+ class Symbol
32
+ [:eql, :not_eql, :gt, :gte, :lt, :lte, :desc, :descending,
33
+ :matches, :does_not_match, :contains, :does_not_contain,
34
+ :substring, :not_substring].each do |operator|
35
+
36
+ define_method(operator) do
37
+ SymbolOperator.new(self, operator)
38
+ end unless method_defined?(operator)
39
+ end
40
+ end
@@ -0,0 +1,67 @@
1
+ ##
2
+ ## cacert.pem-foo -- Bundle of CA Root Certificates
3
+ ##
4
+ ## Converted at: Thu Mar 26 21:23:06 2009 UTC
5
+ ##
6
+ ## This is a bundle of X.509 certificates of public Certificate Authorities
7
+ ## (CA). These were automatically extracted from Mozilla's root certificates
8
+ ## file (certdata.txt). This file can be found in the mozilla source tree:
9
+ ## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt'
10
+ ##
11
+ ## It contains the certificates in PEM format and therefore
12
+ ## can be directly used with curl / libcurl / php_curl, or with
13
+ ## an Apache+mod_ssl webserver for SSL client authentication.
14
+ ## Just configure this file as the SSLCACertificateFile.
15
+ ##
16
+
17
+ # ***** BEGIN LICENSE BLOCK *****
18
+ # Version: MPL 1.1/GPL 2.0/LGPL 2.1
19
+ #
20
+ # The contents of this file are subject to the Mozilla Public License Version
21
+ # 1.1 (the "License"); you may not use this file except in compliance with
22
+ # the License. You may obtain a copy of the License at
23
+ # http://www.mozilla.org/MPL/
24
+ #
25
+ # Software distributed under the License is distributed on an "AS IS" basis,
26
+ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
27
+ # for the specific language governing rights and limitations under the
28
+ # License.
29
+ #
30
+ # The Original Code is the Netscape security libraries.
31
+ #
32
+ # The Initial Developer of the Original Code is
33
+ # Netscape Communications Corporation.
34
+ # Portions created by the Initial Developer are Copyright (C) 1994-2000
35
+ # the Initial Developer. All Rights Reserved.
36
+ #
37
+ # Contributor(s):
38
+ #
39
+ # Alternatively, the contents of this file may be used under the terms of
40
+ # either the GNU General Public License Version 2 or later (the "GPL"), or
41
+ # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
42
+ # in which case the provisions of the GPL or the LGPL are applicable instead
43
+ # of those above. If you wish to allow use of your version of this file only
44
+ # under the terms of either the GPL or the LGPL, and not to allow others to
45
+ # use your version of this file under the terms of the MPL, indicate your
46
+ # decision by deleting the provisions above and replace them with the notice
47
+ # and other provisions required by the GPL or the LGPL. If you do not delete
48
+ # the provisions above, a recipient may use your version of this file under
49
+ # the terms of any one of the MPL, the GPL or the LGPL.
50
+ #
51
+ # ***** END LICENSE BLOCK *****
52
+ # @(#) $RCSfile: certdata.txt,v $ $Revision: 1.51 $ $Date: 2009/01/15 22:35:15 $
53
+
54
+ Verisign/RSA Secure Server CA
55
+ =============================
56
+ -----BEGIN CERTIFICATE-----
57
+ MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
58
+ IDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy
59
+ IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVow
60
+ XzELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQL
61
+ EyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUA
62
+ A4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1Xp
63
+ sSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJ
64
+ VCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw4jrg7HFG
65
+ mhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2
66
+ qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
67
+ -----END CERTIFICATE-----
@@ -0,0 +1,72 @@
1
+ <?xml version='1.0' encoding='UTF-8'?>
2
+ <feed xmlns='http://www.w3.org/2005/Atom' xmlns:dxp='http://schemas.google.com/analytics/2009' xmlns:ga='http://schemas.google.com/ga/2009' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/&quot;A0UASX45cCp7I2A9WxFQEUQ.&quot;' gd:kind='analytics#accounts'>
3
+ <id>http://www.google.com/analytics/feeds/accounts/tpitale@gmail.com</id>
4
+ <updated>2010-05-06T19:27:28.028-07:00</updated>
5
+ <title>Profile list for tpitale@gmail.com</title>
6
+ <link rel='self' type='application/atom+xml' href='https://www.google.com/analytics/feeds/accounts/default'/>
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
+ <dxp:segment id='gaid::-1' name='All Visits'>
13
+ <dxp:definition> </dxp:definition>
14
+ </dxp:segment>
15
+ <dxp:segment id='gaid::-2' name='New Visitors'>
16
+ <dxp:definition>ga:visitorType==New Visitor</dxp:definition>
17
+ </dxp:segment>
18
+ <dxp:segment id='gaid::-3' name='Paid Search Traffic'>
19
+ <dxp:definition>ga:medium==cpa,ga:medium==cpc,ga:medium==cpm,ga:medium==cpp,ga:medium==cpv,ga:medium==ppc</dxp:definition>
20
+ </dxp:segment>
21
+ <dxp:segment id='gaid::0' name='Not New York'>
22
+ <dxp:definition>ga:city!=New York</dxp:definition>
23
+ </dxp:segment>
24
+ <entry gd:etag='W/&quot;CUcHSXs8fip7I2A9WxBUFUg.&quot;' gd:kind='analytics#account'>
25
+ <id>http://www.google.com/analytics/feeds/accounts/ga:12345</id>
26
+ <updated>2008-07-21T14:05:57.000-07:00</updated>
27
+ <title>Historical</title>
28
+ <link rel='alternate' type='text/html' href='http://www.google.com/analytics'/>
29
+ <ga:goal active='true' name='Read Blog' number='1' value='10.0'>
30
+ <ga:destination caseSensitive='false' expression='/blog.html' matchType='head' step1Required='false'/>
31
+ </ga:goal>
32
+ <ga:goal active='true' name='Go for Support' number='2' value='5.0'>
33
+ <ga:destination caseSensitive='false' expression='/support.html' matchType='head' step1Required='false'/>
34
+ </ga:goal>
35
+ <ga:goal active='true' name='Contact Form Submission' number='3' value='100.0'>
36
+ <ga:destination caseSensitive='false' expression='/contact-submit' matchType='exact' step1Required='true'>
37
+ <ga:step name='Contact Form Page' number='1' path='/contact.html'/>
38
+ </ga:destination>
39
+ </ga:goal>
40
+ <ga:goal active='true' name='Newsletter Form Submission' number='4' value='25.0'>
41
+ <ga:destination caseSensitive='false' expression='/newsletter-submit' matchType='exact' step1Required='false'/>
42
+ </ga:goal>
43
+ <dxp:property name="ga:accountId" value="1111"/>
44
+ <dxp:property name="ga:accountName" value="Blog Beta"/>
45
+ <dxp:property name="ga:profileId" value="1212"/>
46
+ <dxp:property name="ga:webPropertyId" value="UA-1111-1"/>
47
+ <dxp:property name="ga:currency" value="USD"/>
48
+ <dxp:property name="ga:timezone" value="America/New_York"/>
49
+ <dxp:tableId>ga:12345</dxp:tableId>
50
+ </entry>
51
+ <entry gd:etag='W/&quot;A0UASX45cCp7I2A9WxFQEUQ.&quot;' gd:kind='analytics#account'>
52
+ <id>http://www.google.com/analytics/feeds/accounts/ga:12346</id>
53
+ <updated>2008-11-24T11:51:07.000-08:00</updated>
54
+ <title>Presently</title>
55
+ <link rel='alternate' type='text/html' href='http://www.google.com/analytics'/>
56
+ <ga:goal active='true' name='Contact Form Submission' number='1' value='100.0'>
57
+ <ga:destination caseSensitive='false' expression='/contact-submit' matchType='exact' step1Required='true'>
58
+ <ga:step name='Contact Form Page' number='1' path='/contact.html'/>
59
+ </ga:destination>
60
+ </ga:goal>
61
+ <ga:goal active='true' name='Newsletter Form Submission' number='2' value='25.0'>
62
+ <ga:destination caseSensitive='false' expression='/newsletter-submit' matchType='exact' step1Required='false'/>
63
+ </ga:goal>
64
+ <dxp:property name="ga:accountId" value="1111"/>
65
+ <dxp:property name="ga:accountName" value="Blog Beta"/>
66
+ <dxp:property name="ga:profileId" value="1213"/>
67
+ <dxp:property name="ga:webPropertyId" value="UA-1111-2"/>
68
+ <dxp:property name="ga:currency" value="USD"/>
69
+ <dxp:property name="ga:timezone" value="America/New_York"/>
70
+ <dxp:tableId>ga:12346</dxp:tableId>
71
+ </entry>
72
+ </feed>