titanous-garb 0.8.5

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