readmedia-garb 0.9.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/README.md +198 -0
  2. data/Rakefile +57 -0
  3. data/lib/garb/account.rb +22 -0
  4. data/lib/garb/account_feed_request.rb +25 -0
  5. data/lib/garb/authentication_request.rb +53 -0
  6. data/lib/garb/data_request.rb +42 -0
  7. data/lib/garb/destination.rb +18 -0
  8. data/lib/garb/filter_parameters.rb +42 -0
  9. data/lib/garb/goal.rb +20 -0
  10. data/lib/garb/management/account.rb +39 -0
  11. data/lib/garb/management/feed.rb +26 -0
  12. data/lib/garb/management/goal.rb +44 -0
  13. data/lib/garb/management/profile.rb +44 -0
  14. data/lib/garb/management/segment.rb +27 -0
  15. data/lib/garb/management/web_property.rb +38 -0
  16. data/lib/garb/model.rb +91 -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 +56 -0
  22. data/lib/garb/reports/bounces.rb +5 -0
  23. data/lib/garb/reports/exits.rb +5 -0
  24. data/lib/garb/reports/pageviews.rb +5 -0
  25. data/lib/garb/reports/unique_pageviews.rb +5 -0
  26. data/lib/garb/reports/visits.rb +5 -0
  27. data/lib/garb/reports.rb +5 -0
  28. data/lib/garb/resource.rb +115 -0
  29. data/lib/garb/result_set.rb +21 -0
  30. data/lib/garb/session.rb +25 -0
  31. data/lib/garb/step.rb +13 -0
  32. data/lib/garb/version.rb +14 -0
  33. data/lib/garb.rb +69 -0
  34. data/lib/support.rb +40 -0
  35. data/test/fixtures/cacert.pem +67 -0
  36. data/test/fixtures/profile_feed.xml +72 -0
  37. data/test/fixtures/report_feed.xml +48 -0
  38. data/test/test_helper.rb +45 -0
  39. data/test/unit/garb/account_feed_request_test.rb +42 -0
  40. data/test/unit/garb/account_test.rb +53 -0
  41. data/test/unit/garb/authentication_request_test.rb +121 -0
  42. data/test/unit/garb/data_request_test.rb +107 -0
  43. data/test/unit/garb/destination_test.rb +28 -0
  44. data/test/unit/garb/filter_parameters_test.rb +68 -0
  45. data/test/unit/garb/goal_test.rb +24 -0
  46. data/test/unit/garb/management/account_test.rb +70 -0
  47. data/test/unit/garb/management/feed_test.rb +44 -0
  48. data/test/unit/garb/management/goal_test.rb +81 -0
  49. data/test/unit/garb/management/profile_test.rb +81 -0
  50. data/test/unit/garb/management/segment_test.rb +47 -0
  51. data/test/unit/garb/management/web_property_test.rb +64 -0
  52. data/test/unit/garb/model_test.rb +141 -0
  53. data/test/unit/garb/oauth_session_test.rb +11 -0
  54. data/test/unit/garb/profile_reports_test.rb +29 -0
  55. data/test/unit/garb/profile_test.rb +77 -0
  56. data/test/unit/garb/report_parameter_test.rb +43 -0
  57. data/test/unit/garb/report_response_test.rb +47 -0
  58. data/test/unit/garb/report_test.rb +99 -0
  59. data/test/unit/garb/resource_test.rb +50 -0
  60. data/test/unit/garb/session_test.rb +84 -0
  61. data/test/unit/garb/step_test.rb +15 -0
  62. data/test/unit/garb_test.rb +26 -0
  63. data/test/unit/symbol_operator_test.rb +37 -0
  64. metadata +189 -0
@@ -0,0 +1,38 @@
1
+ module Garb
2
+ module Management
3
+ class WebProperty
4
+ attr_accessor :session, :path
5
+ attr_accessor :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_from_entry(entry, session)}
10
+ end
11
+
12
+ def self.for_account(account)
13
+ all(account.session, account.path+'/webproperties')
14
+ end
15
+
16
+ def self.new_from_entry(entry, session)
17
+ web_property = new
18
+ web_property.session = session
19
+ web_property.path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '')
20
+ web_property.properties = Garb.parse_properties(entry)
21
+ web_property
22
+ end
23
+
24
+ def properties=(properties)
25
+ self.id = properties["web_property_id"]
26
+ self.account_id = properties["account_id"]
27
+ end
28
+
29
+ def profiles
30
+ @profiles ||= Profile.for_web_property(self)
31
+ end
32
+
33
+ def goals
34
+ @goals ||= Goal.for_web_property(self)
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/garb/model.rb ADDED
@@ -0,0 +1,91 @@
1
+ module Garb
2
+ module Model
3
+ MONTH = 2592000
4
+ URL = "https://www.googleapis.com/analytics/v2.4/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
+ start_date = options.fetch(:start_date, Time.now - MONTH)
30
+ end_date = options.fetch(:end_date, Time.now)
31
+ default_params = build_default_params(profile, start_date, end_date)
32
+
33
+ param_set = [
34
+ default_params,
35
+ metrics.to_params,
36
+ dimensions.to_params,
37
+ parse_filters(options).to_params,
38
+ parse_segment(options),
39
+ parse_sort(options).to_params,
40
+ build_page_params(options)
41
+ ]
42
+
43
+ data = send_request_for_data(profile, build_params(param_set))
44
+ ReportResponse.new(data, instance_klass).results
45
+ end
46
+
47
+ private
48
+ def send_request_for_data(profile, params)
49
+ request = DataRequest.new(profile.session, URL, params)
50
+ response = request.send_request
51
+ response.body
52
+ end
53
+
54
+ def build_params(param_set)
55
+ param_set.inject({}) {|p,i| p.merge(i)}.reject{|k,v| v.nil?}
56
+ end
57
+
58
+ def parse_filters(options)
59
+ filters = FilterParameters.new
60
+ filters.parameters << options[:filters] if options.has_key?(:filters)
61
+ filters
62
+ end
63
+
64
+ def parse_segment(options)
65
+ segment_id = "gaid::#{options[:segment_id].to_i}" if options.has_key?(:segment_id)
66
+ {'segment' => segment_id}
67
+ end
68
+
69
+ def parse_sort(options)
70
+ sort = ReportParameter.new(:sort)
71
+ sort << options[:sort] if options.has_key?(:sort)
72
+ sort
73
+ end
74
+
75
+ def build_default_params(profile, start_date, end_date)
76
+ {
77
+ 'ids' => Garb.to_ga(profile.id),
78
+ 'start-date' => format_time(start_date),
79
+ 'end-date' => format_time(end_date)
80
+ }
81
+ end
82
+
83
+ def build_page_params(options)
84
+ {'max-results' => options[:limit], 'start-index' => options[:offset]}
85
+ end
86
+
87
+ def format_time(t)
88
+ t.strftime('%Y-%m-%d')
89
+ end
90
+ end
91
+ 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.to_s.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
@@ -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,56 @@
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
+ if @results.nil?
12
+ @results = ResultSet.new(parse)
13
+ @results.total_results = parse_total_results
14
+ @results.sampled = parse_sampled_flag
15
+ end
16
+
17
+ @results
18
+ end
19
+
20
+ def sampled?
21
+ end
22
+
23
+ private
24
+ def parse
25
+ entries.map do |entry|
26
+ @instance_klass.new(Hash[
27
+ values_for(entry).map {|v| [Garb.from_ga(v['name']), v['value']]}
28
+ ])
29
+ end
30
+ end
31
+
32
+ def entries
33
+ feed? ? [parsed_xml['feed']['entry']].flatten.compact : []
34
+ end
35
+
36
+ def parse_total_results
37
+ feed? ? parsed_xml['feed']['openSearch:totalResults'].to_i : 0
38
+ end
39
+
40
+ def parse_sampled_flag
41
+ feed? ? (parsed_xml['feed']['dxp:containsSampledData'] == 'true') : false
42
+ end
43
+
44
+ def parsed_xml
45
+ @parsed_xml ||= Crack::XML.parse(@xml)
46
+ end
47
+
48
+ def feed?
49
+ !parsed_xml['feed'].nil?
50
+ end
51
+
52
+ def values_for(entry)
53
+ KEYS.map {|k| entry[k]}.flatten.compact
54
+ end
55
+ end
56
+ end
@@ -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,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,115 @@
1
+ module Garb
2
+ module Resource
3
+ MONTH = 2592000
4
+ URL = "https://www.googleapis.com/analytics/v2.4/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("The use of Garb::Resource will be removed in favor of 'extend Garb::Model'")
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,21 @@
1
+ module Garb
2
+ class ResultSet
3
+ include Enumerable
4
+
5
+ attr_accessor :total_results, :sampled
6
+
7
+ alias :sampled? :sampled
8
+
9
+ def initialize(results)
10
+ @results = results
11
+ end
12
+
13
+ def each(&block)
14
+ @results.each(&block)
15
+ end
16
+
17
+ def to_a
18
+ @results
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
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
+ end
21
+
22
+ include Methods
23
+ extend Methods
24
+ end
25
+ 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,14 @@
1
+ module Garb
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 9
6
+ TINY = 1
7
+ NANO = 1
8
+
9
+ def self.to_s # :nodoc:
10
+ [MAJOR, MINOR, TINY, NANO].join('.')
11
+ end
12
+
13
+ end
14
+ end
data/lib/garb.rb ADDED
@@ -0,0 +1,69 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ require 'cgi'
5
+ require 'ostruct'
6
+ require 'crack'
7
+
8
+ begin
9
+ require 'active_support/inflector'
10
+ require 'active_support/deprecation'
11
+ rescue Exception => e
12
+ require 'active_support'
13
+ end
14
+
15
+ require 'garb/version'
16
+ require 'garb/authentication_request'
17
+ require 'garb/data_request'
18
+ require 'garb/account_feed_request'
19
+ require 'garb/session'
20
+ require 'garb/profile_reports'
21
+ require 'garb/step'
22
+ require 'garb/destination'
23
+ require 'garb/goal'
24
+ require 'garb/profile'
25
+ require 'garb/account'
26
+ require 'garb/filter_parameters'
27
+ require 'garb/report_parameter'
28
+ require 'garb/result_set'
29
+ require 'garb/report_response'
30
+ require 'garb/resource'
31
+ require 'garb/report'
32
+
33
+ require 'garb/model'
34
+
35
+ # management
36
+ require 'garb/management/feed'
37
+ require 'garb/management/segment'
38
+ require 'garb/management/account'
39
+ require 'garb/management/web_property'
40
+ require 'garb/management/profile'
41
+ require 'garb/management/goal'
42
+
43
+ require 'support'
44
+
45
+ module Garb
46
+ GA = "http://schemas.google.com/analytics/2008"
47
+
48
+ extend self
49
+
50
+ def to_google_analytics(thing)
51
+ return thing.to_google_analytics if thing.respond_to?(:to_google_analytics)
52
+
53
+ "ga:#{thing.to_s.camelize(:lower)}"
54
+ end
55
+ alias :to_ga :to_google_analytics
56
+
57
+ def from_google_analytics(thing)
58
+ thing.to_s.gsub(/^ga\:/, '').underscore
59
+ end
60
+ alias :from_ga :from_google_analytics
61
+
62
+ def parse_properties(entry)
63
+ Hash[entry['dxp:property'].map {|p| [Garb.from_ga(p['name']),p['value']]}]
64
+ end
65
+
66
+ def parse_link(entry, rel)
67
+ entry['link'].detect {|link| link["rel"] == rel}['href']
68
+ end
69
+ 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