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,42 @@
1
+ module Garb
2
+ class DataRequest
3
+ class ClientError < StandardError; end
4
+
5
+ def initialize(session, base_url, parameters={})
6
+ @session = session
7
+ @base_url = base_url
8
+ @parameters = parameters
9
+ end
10
+
11
+ def query_string
12
+ parameter_list = @parameters.map {|k,v| "#{k}=#{v}" }
13
+ parameter_list.empty? ? '' : "?#{parameter_list.join('&')}"
14
+ end
15
+
16
+ def uri
17
+ URI.parse(@base_url)
18
+ end
19
+
20
+ def send_request
21
+ response = if @session.single_user?
22
+ single_user_request(@session.auth_sub? ? 'AuthSub' : 'GoogleLogin')
23
+ elsif @session.oauth_user?
24
+ oauth_user_request
25
+ end
26
+
27
+ raise ClientError, response.body.inspect unless response.kind_of?(Net::HTTPSuccess)
28
+ response
29
+ end
30
+
31
+ def single_user_request(auth='GoogleLogin')
32
+ http = Net::HTTP.new(uri.host, uri.port)
33
+ http.use_ssl = true
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
35
+ http.get("#{uri.path}#{query_string}", { 'Authorization' => "#{auth} #{auth == 'GoogleLogin' ? 'auth' : 'token'}=#{@session.auth_token}", 'GData-Version' => '2' })
36
+ end
37
+
38
+ def oauth_user_request
39
+ @session.access_token.get("#{uri}#{query_string}", {'GData-Version' => '2'})
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ module Garb
2
+ class Destination
3
+ attr_reader :match_type, :expression, :steps
4
+
5
+ def initialize(attributes)
6
+ return unless attributes.is_a?(Hash)
7
+
8
+ @match_type = attributes['matchType']
9
+ @expression = attributes['expression']
10
+ @case_sensitive = (attributes['caseSensitive'] == 'true')
11
+
12
+ step_attributes = attributes[Garb.to_ga('step')]
13
+ @steps = Array(step_attributes.is_a?(Hash) ? [step_attributes] : step_attributes).map {|s| Step.new(s)}
14
+ end
15
+
16
+ def case_sensitive?
17
+ @case_sensitive
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ module Garb
2
+ class FilterParameters
3
+ def self.define_operators(*methods)
4
+ methods.each do |method|
5
+ class_eval <<-CODE
6
+ def #{method}(field, value)
7
+ @filter_hash.merge!({SymbolOperator.new(field, :#{method}) => value})
8
+ end
9
+ CODE
10
+ end
11
+ end
12
+
13
+ define_operators :eql, :not_eql, :gt, :gte, :lt, :lte, :matches,
14
+ :does_not_match, :contains, :does_not_contain, :substring, :not_substring
15
+
16
+ attr_accessor :parameters
17
+
18
+ def initialize
19
+ self.parameters = []
20
+ end
21
+
22
+ def filters(&block)
23
+ @filter_hash = {}
24
+
25
+ instance_eval &block
26
+
27
+ self.parameters << @filter_hash
28
+ end
29
+
30
+ def to_params
31
+ value = self.parameters.map do |param|
32
+ param.map do |k,v|
33
+ next unless k.is_a?(SymbolOperator)
34
+ "#{URI.encode(k.to_google_analytics, /[=<>]/)}#{CGI::escape(v.to_s)}"
35
+ end.join('%3B') # Hash AND (no duplicate keys), escape char for ';' fixes oauth
36
+ end.join(',') # Array OR
37
+
38
+ value.empty? ? {} : {'filters' => value}
39
+ end
40
+ end
41
+ end
data/lib/garb/goal.rb ADDED
@@ -0,0 +1,20 @@
1
+ module Garb
2
+ class Goal
3
+ attr_reader :name, :number, :value, :destination
4
+
5
+ def initialize(attributes={})
6
+ return unless attributes.is_a?(Hash)
7
+
8
+ @name = attributes['name']
9
+ @number = attributes['number'].to_i
10
+ @value = attributes['value'].to_f
11
+ @active = (attributes['active'] == 'true')
12
+
13
+ @destination = Destination.new(attributes[Garb.to_ga('destination')])
14
+ end
15
+
16
+ def active?
17
+ @active
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module Garb
2
+ module Management
3
+ class Account
4
+ attr_reader :session, :path
5
+ attr_reader :id, :title, :name
6
+
7
+ def self.all(session = Session)
8
+ feed = Feed.new(session, '/accounts') # builds request and parses response
9
+
10
+ feed.entries.map {|entry| new(entry, session)}
11
+ end
12
+
13
+ def initialize(entry, session)
14
+ @session = session
15
+ @path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '')
16
+ @title = entry['title'].gsub('Google Analytics Account ', '')
17
+
18
+ properties = Garb.parse_properties(entry)
19
+ @id = properties["account_id"]
20
+ @name = properties["account_name"]
21
+ end
22
+
23
+ def web_properties
24
+ @web_properties ||= WebProperty.for_account(self) # will call path
25
+ end
26
+
27
+ def profiles
28
+ @profiles ||= Profile.for_account(self)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ module Garb
2
+ module Management
3
+ class Feed
4
+ BASE_URL = "https://www.google.com/analytics/feeds/datasources/ga"
5
+
6
+ attr_reader :request
7
+
8
+ def initialize(session, path)
9
+ @request = DataRequest.new(session, BASE_URL+path)
10
+ end
11
+
12
+ def parsed_response
13
+ @parsed_response ||= Crack::XML.parse(response.body)
14
+ end
15
+
16
+ def entries
17
+ # possible to have nil entries, yuck
18
+ parsed_response ? [parsed_response['feed']['entry']].flatten.reject {|e| e.nil?} : []
19
+ end
20
+
21
+ def response
22
+ @response ||= request.send_request
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module Garb
2
+ class Goal
3
+ attr_reader :name, :number, :value, :destination
4
+
5
+ def initialize(attributes={})
6
+ return unless attributes.is_a?(Hash)
7
+
8
+ @name = attributes['name']
9
+ @number = attributes['number'].to_i
10
+ @value = attributes['value'].to_f
11
+ @active = (attributes['active'] == 'true')
12
+
13
+ @destination = Destination.new(attributes[Garb.to_ga('destination')])
14
+ end
15
+
16
+ def active?
17
+ @active
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ module Garb
2
+ module Management
3
+ class Profile
4
+
5
+ include ProfileReports
6
+
7
+ attr_reader :session, :path
8
+ attr_reader :id, :table_id, :title, :account_id, :web_property_id
9
+
10
+ def self.all(session = Session, path = '/accounts/~all/webproperties/~all/profiles')
11
+ feed = Feed.new(session, path)
12
+ feed.entries.map {|entry| new(entry, session)}
13
+ end
14
+
15
+ def self.for_account(account)
16
+ all(account.session, account.path+'/webproperties/~all/profiles')
17
+ end
18
+
19
+ def self.for_web_property(web_property)
20
+ all(web_property.session, web_property.path+'/profiles')
21
+ end
22
+
23
+ def initialize(entry, session)
24
+ @session = session
25
+ @path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '')
26
+
27
+ properties = Garb.parse_properties(entry)
28
+ @id = properties['profile_id']
29
+ @table_id = properties['table_id']
30
+ @title = properties['profile_name']
31
+ @account_id = properties['account_id']
32
+ @web_property_id = properties['web_property_id']
33
+ end
34
+
35
+ # def goals
36
+ # end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ module Garb
2
+ module Management
3
+ class WebProperty
4
+ attr_reader :session, :path
5
+ attr_reader :id, :account_id
6
+
7
+ def self.all(session = Session, path='/accounts/~all/webproperties')
8
+ feed = Feed.new(session, path)
9
+ feed.entries.map {|entry| new(entry, session)}
10
+ end
11
+
12
+ def self.for_account(account)
13
+ all(account.session, account.path+'/webproperties')
14
+ end
15
+
16
+ def initialize(entry, session)
17
+ @session = session
18
+ @path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '')
19
+
20
+ properties = Garb.parse_properties(entry)
21
+ @id = properties["web_property_id"]
22
+ @account_id = properties["account_id"]
23
+ end
24
+
25
+ def profiles
26
+ @profiles ||= Profile.for_web_property(self)
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/garb/model.rb ADDED
@@ -0,0 +1,89 @@
1
+ module Garb
2
+ module Model
3
+ MONTH = 2592000
4
+ URL = "https://www.google.com/analytics/feeds/data"
5
+
6
+ def self.extended(base)
7
+ ProfileReports.add_report_method(base)
8
+ end
9
+
10
+ def metrics(*fields)
11
+ @metrics ||= ReportParameter.new(:metrics)
12
+ @metrics << fields
13
+ end
14
+
15
+ def dimensions(*fields)
16
+ @dimensions ||= ReportParameter.new(:dimensions)
17
+ @dimensions << fields
18
+ end
19
+
20
+ def set_instance_klass(klass)
21
+ @instance_klass = klass
22
+ end
23
+
24
+ def instance_klass
25
+ @instance_klass || OpenStruct
26
+ end
27
+
28
+ def results(profile, options = {})
29
+ default_params = build_default_params(profile)
30
+
31
+ param_set = [
32
+ default_params,
33
+ metrics.to_params,
34
+ dimensions.to_params,
35
+ parse_filters(options).to_params,
36
+ parse_segment(options),
37
+ parse_sort(options).to_params,
38
+ build_page_params(options)
39
+ ]
40
+
41
+ data = send_request_for_data(profile, build_params(param_set))
42
+ ReportResponse.new(data, instance_klass).results
43
+ end
44
+
45
+ private
46
+ def send_request_for_data(profile, params)
47
+ request = DataRequest.new(profile.session, URL, params)
48
+ response = request.send_request
49
+ response.body
50
+ end
51
+
52
+ def build_params(param_set)
53
+ param_set.inject({}) {|p,i| p.merge(i)}.reject{|k,v| v.nil?}
54
+ end
55
+
56
+ def parse_filters(options)
57
+ filters = FilterParameters.new
58
+ filters.parameters << options[:filters] if options.has_key?(:filters)
59
+ filters
60
+ end
61
+
62
+ def parse_segment(options)
63
+ segment_id = "gaid::#{options[:segment_id].to_i}" if options.has_key?(:segment_id)
64
+ {'segment' => segment_id}
65
+ end
66
+
67
+ def parse_sort(options)
68
+ sort = ReportParameter.new(:sort)
69
+ sort << options[:sort] if options.has_key?(:sort)
70
+ sort
71
+ end
72
+
73
+ def build_default_params(profile)
74
+ {
75
+ 'ids' => Garb.to_ga(profile.id),
76
+ 'start-date' => format_time(Time.now - Model::MONTH),
77
+ 'end-date' => format_time(Time.now)
78
+ }
79
+ end
80
+
81
+ def build_page_params(options)
82
+ {'max-results' => options[:limit], 'start-index' => options[:offset]}
83
+ end
84
+
85
+ def format_time(t)
86
+ t.strftime('%Y-%m-%d')
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,33 @@
1
+ module Garb
2
+ class Profile
3
+
4
+ include ProfileReports
5
+
6
+ attr_reader :session, :table_id, :title, :account_name, :account_id, :web_property_id, :goals
7
+
8
+ def initialize(entry, session)
9
+ @session = session
10
+ @title = entry['title']
11
+ @table_id = entry['dxp:tableId']
12
+ @goals = (entry[Garb.to_ga('goal')] || []).map {|g| Goal.new(g)}
13
+
14
+ Garb.parse_properties(entry).each do |k,v|
15
+ instance_variable_set :"@#{k}", v
16
+ end
17
+ end
18
+
19
+ def id
20
+ Garb.from_ga(@table_id)
21
+ end
22
+
23
+ def self.all(session = Session)
24
+ ActiveSupport::Deprecation.warn("Garb::Profile.all is deprecated in favor of Garb::Management::Profile.all")
25
+ AccountFeedRequest.new(session).entries.map {|entry| new(entry, session)}
26
+ end
27
+
28
+ def self.first(id, session = Session)
29
+ ActiveSupport::Deprecation.warn("Garb::Profile.first is deprecated in favor of Garb::Management::WebProperty")
30
+ all(session).detect {|profile| profile.id == id || profile.web_property_id == id }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ module Garb
2
+ module ProfileReports
3
+ def self.add_report_method(klass)
4
+ # demodulize leaves potential to redefine
5
+ # these methods given different namespaces
6
+ method_name = klass.name.demodulize.underscore
7
+ return unless method_name.length > 0
8
+
9
+ class_eval <<-CODE
10
+ def #{method_name}(opts = {}, &block)
11
+ #{klass}.results(self, opts, &block)
12
+ end
13
+ CODE
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ module Garb
2
+ class Report
3
+ include Resource
4
+
5
+ MONTH = 2592000
6
+ URL = "https://www.google.com/analytics/feeds/data"
7
+
8
+ def initialize(profile, opts={})
9
+ ActiveSupport::Deprecation.warn("The use of Report will be removed in favor of 'extend Garb::Model'")
10
+
11
+ @profile = profile
12
+
13
+ @start_date = opts.fetch(:start_date, Time.now - MONTH)
14
+ @end_date = opts.fetch(:end_date, Time.now)
15
+ @limit = opts.fetch(:limit, nil)
16
+ @offset = opts.fetch(:offset, nil)
17
+
18
+ metrics opts.fetch(:metrics, [])
19
+ dimensions opts.fetch(:dimensions, [])
20
+ sort opts.fetch(:sort, [])
21
+ end
22
+
23
+ def results
24
+ ReportResponse.new(send_request_for_body).results
25
+ end
26
+
27
+ end
28
+ end