rugalytics 0.0.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/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ v0.1.0. initial release
2
+
3
+ v0.0.0. forked from Statwhore http://github.com/jnunemaker/statwhore
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Rob McKinnon
2
+ Copyright (c) 2007 John Nunemaker
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,24 @@
1
+ CHANGELOG
2
+ lib/rugalytics/account.rb
3
+ lib/rugalytics/analytics.rb
4
+ lib/rugalytics/connection.rb
5
+ lib/rugalytics/graph.rb
6
+ lib/rugalytics/item.rb
7
+ lib/rugalytics/profile.rb
8
+ lib/rugalytics/report.rb
9
+ lib/rugalytics.rb
10
+ LICENSE
11
+ Manifest
12
+ README
13
+ README.rdoc
14
+ spec/fixtures/analytics_account_find_all.html
15
+ spec/fixtures/analytics_profile_find_all.html
16
+ spec/fixtures/dashboard_report_webgroup.xml
17
+ spec/lib/rugalytics/account_spec.rb
18
+ spec/lib/rugalytics/graph_spec.rb
19
+ spec/lib/rugalytics/item_spec.rb
20
+ spec/lib/rugalytics/profile_spec.rb
21
+ spec/lib/rugalytics/report_spec.rb
22
+ spec/lib/rugalytics_spec.rb
23
+ spec/spec.opts
24
+ spec/spec_helper.rb
data/README ADDED
@@ -0,0 +1,128 @@
1
+ Rugalytics is a Ruby API for Google Analytics.
2
+
3
+ = Warning - API under development
4
+
5
+ The Rugalytics API is in early development so it may change slightly over time.
6
+ It should be in working order, so please give it a test spin!
7
+
8
+ The source code is hosted at github. Feel free to fork the code if you have
9
+ something to contribute:
10
+
11
+ http://github.com/robmckinnon/rugalytics
12
+
13
+
14
+ == Install as a Gem
15
+
16
+ Should be up at rubyforge, so to install:
17
+
18
+ sudo gem install rugalytics
19
+
20
+
21
+ == Authenticate
22
+
23
+ Login with your Google Analytics user name and password:
24
+
25
+ require 'rugalytics'
26
+
27
+ Rugalytics.login 'username', 'password'
28
+
29
+
30
+ == Obtain Profile
31
+
32
+ Get profile using account name and profile name:
33
+
34
+ profile = Rugalytics.find_profile('your_site.com', 'blog.your_site.com')
35
+
36
+
37
+ If account name and profile name are the same:
38
+
39
+ profile = Rugalytics.find_profile('your_site.com')
40
+
41
+
42
+ == Get Profile Summary Statistics
43
+
44
+ Obtaining page views:
45
+
46
+ profile.pageviews # default period is one month ending today
47
+ => 160600
48
+
49
+ profile.pageviews :from=>'2007-01-01'
50
+ => 2267550
51
+
52
+ profile.pageviews :from=>'2007-01-01', :to=>'2007-01-02'
53
+ => 24980
54
+
55
+ The +pageviews+ method is doing this under the hood:
56
+
57
+ report = profile.load_report('Pageviews', :from=>'2007-01-01', :to=>'2007-01-02')
58
+
59
+ report.page_views_total
60
+ => 24980
61
+
62
+ And under the hood, the +page_views_total+ method is actually calling:
63
+
64
+ report.page_views_graph.sum_of_points
65
+ => 24980
66
+
67
+ == Load a Report
68
+
69
+ The report name, e.g. 'Pageviews' or 'TrafficSources', is the rpt parameter from
70
+ the Google Analytics URL for a CSV report export, e.g.:
71
+
72
+ https://www.google.com/analytics/reporting/export?fmt=2&...&&rpt=TrafficSourcesReport&...
73
+
74
+ From a page on the Analytics website, you can find the CSV URL by clicking on
75
+ the Export tab, and then mousing over the CSV option.
76
+
77
+ Let's load the TrafficSources report:
78
+
79
+ report = profile.load_report 'TrafficSources'
80
+
81
+ report.report_name
82
+ => "Traffic Sources Overview"
83
+
84
+ report.start_date
85
+ => "28 May 2008"
86
+
87
+ report.end_date
88
+ => "4 June 2008"
89
+
90
+ report.source_items.collect{|s| "#{s.sources}: #{s.visits}"}.first
91
+ => "google (organic): 15210"
92
+
93
+ report.keyword_items.collect{|k| "#{k.keywords}: #{k.visits}"}[1]
94
+ => "oecd nz report summary 2007: 14"
95
+
96
+ Let's try another report, VisitorsOverview:
97
+
98
+ report = profile.load_report 'VisitorsOverview'
99
+
100
+ report.browser_items[1]
101
+ => #<Rugalytics::Item @percentage_visits="0.18", @visits="3140", @browser="Firefox">
102
+
103
+ report.connection_speed_items[3]
104
+ => #<Rugalytics::Item @connection_speed="Dialup", @percentage_visits="0.06340057402849197", @visits="1100">
105
+
106
+ Let's now grab 100 lines of the Networks report:
107
+
108
+ report = profile.load_report 'Networks', :rows=>100
109
+
110
+ report.items.size
111
+ => 100
112
+
113
+ report.items.first.network_location
114
+ => "telecom xtra"
115
+
116
+
117
+ ==Acknowledgements
118
+
119
+ Rugalytics started life as a fork of jnunemaker's Statwhore. As the code and
120
+ project scope began to diverge significantly from Statwhore, a new project was
121
+ initiated.
122
+
123
+ Rugalytics makes use of the Morph gem to emerge Ruby class definitions at
124
+ runtime based on the contents of the CSV reports from Google Analytics.
125
+
126
+ ==License
127
+
128
+ See LICENSE for the terms of this software.
data/README.rdoc ADDED
@@ -0,0 +1,128 @@
1
+ Rugalytics is a Ruby API for Google Analytics.
2
+
3
+ = Warning - API under development
4
+
5
+ The Rugalytics API is in early development so it may change slightly over time.
6
+ It should be in working order, so please give it a test spin!
7
+
8
+ The source code is hosted at github. Feel free to fork the code if you have
9
+ something to contribute:
10
+
11
+ http://github.com/robmckinnon/rugalytics
12
+
13
+
14
+ == Install as a Gem
15
+
16
+ Should be up at rubyforge, so to install:
17
+
18
+ sudo gem install rugalytics
19
+
20
+
21
+ == Authenticate
22
+
23
+ Login with your Google Analytics user name and password:
24
+
25
+ require 'rugalytics'
26
+
27
+ Rugalytics.login 'username', 'password'
28
+
29
+
30
+ == Obtain Profile
31
+
32
+ Get profile using account name and profile name:
33
+
34
+ profile = Rugalytics.find_profile('your_site.com', 'blog.your_site.com')
35
+
36
+
37
+ If account name and profile name are the same:
38
+
39
+ profile = Rugalytics.find_profile('your_site.com')
40
+
41
+
42
+ == Get Profile Summary Statistics
43
+
44
+ Obtaining page views:
45
+
46
+ profile.pageviews # default period is one month ending today
47
+ => 160600
48
+
49
+ profile.pageviews :from=>'2007-01-01'
50
+ => 2267550
51
+
52
+ profile.pageviews :from=>'2007-01-01', :to=>'2007-01-02'
53
+ => 24980
54
+
55
+ The +pageviews+ method is doing this under the hood:
56
+
57
+ report = profile.load_report('Pageviews', :from=>'2007-01-01', :to=>'2007-01-02')
58
+
59
+ report.page_views_total
60
+ => 24980
61
+
62
+ And under the hood, the +page_views_total+ method is actually calling:
63
+
64
+ report.page_views_graph.sum_of_points
65
+ => 24980
66
+
67
+ == Load a Report
68
+
69
+ The report name, e.g. 'Pageviews' or 'TrafficSources', is the rpt parameter from
70
+ the Google Analytics URL for a CSV report export, e.g.:
71
+
72
+ https://www.google.com/analytics/reporting/export?fmt=2&...&&rpt=TrafficSourcesReport&...
73
+
74
+ From a page on the Analytics website, you can find the CSV URL by clicking on
75
+ the Export tab, and then mousing over the CSV option.
76
+
77
+ Let's load the TrafficSources report:
78
+
79
+ report = profile.load_report 'TrafficSources'
80
+
81
+ report.report_name
82
+ => "Traffic Sources Overview"
83
+
84
+ report.start_date
85
+ => "28 May 2008"
86
+
87
+ report.end_date
88
+ => "4 June 2008"
89
+
90
+ report.source_items.collect{|s| "#{s.sources}: #{s.visits}"}.first
91
+ => "google (organic): 15210"
92
+
93
+ report.keyword_items.collect{|k| "#{k.keywords}: #{k.visits}"}[1]
94
+ => "oecd nz report summary 2007: 14"
95
+
96
+ Let's try another report, VisitorsOverview:
97
+
98
+ report = profile.load_report 'VisitorsOverview'
99
+
100
+ report.browser_items[1]
101
+ => #<Rugalytics::Item @percentage_visits="0.18", @visits="3140", @browser="Firefox">
102
+
103
+ report.connection_speed_items[3]
104
+ => #<Rugalytics::Item @connection_speed="Dialup", @percentage_visits="0.06340057402849197", @visits="1100">
105
+
106
+ Let's now grab 100 lines of the Networks report:
107
+
108
+ report = profile.load_report 'Networks', :rows=>100
109
+
110
+ report.items.size
111
+ => 100
112
+
113
+ report.items.first.network_location
114
+ => "telecom xtra"
115
+
116
+
117
+ ==Acknowledgements
118
+
119
+ Rugalytics started life as a fork of jnunemaker's Statwhore. As the code and
120
+ project scope began to diverge significantly from Statwhore, a new project was
121
+ initiated.
122
+
123
+ Rugalytics makes use of the Morph gem to emerge Ruby class definitions at
124
+ runtime based on the contents of the CSV reports from Google Analytics.
125
+
126
+ ==License
127
+
128
+ See LICENSE for the terms of this software.
@@ -0,0 +1,42 @@
1
+ module Rugalytics
2
+ class Account < ::Google::Base
3
+
4
+ class << self
5
+ def find_all
6
+ doc = Hpricot::XML get('https://www.google.com:443/analytics/home/')
7
+ (doc/'select[@name=account_list] option').inject([]) do |accounts, option|
8
+ account_id = option['value'].to_i
9
+ accounts << new(:account_id => account_id, :name => option.inner_html) if account_id > 0
10
+ accounts
11
+ end
12
+ end
13
+
14
+ def find account_id_or_name
15
+ matchs = find_all.select{|a| a.name == account_id_or_name || a.account_id.to_s == account_id_or_name.to_s }
16
+ matchs.empty? ? nil : matchs.first
17
+ end
18
+ end
19
+
20
+ attr_accessor :name, :account_id
21
+
22
+ def initialize(attrs)
23
+ @name = attrs[:name]
24
+ @account_id = attrs[:account_id]
25
+ end
26
+
27
+ def profiles(force=false)
28
+ if force || @profiles.nil?
29
+ @profiles = Profile.find_all(account_id)
30
+ end
31
+ @profiles
32
+ end
33
+
34
+ def find_profile profile_id_or_name
35
+ profiles.detect { |p| p.profile_id.to_s == profile_id_or_name.to_s || p.name == profile_id_or_name }
36
+ end
37
+
38
+ def to_s
39
+ "#{name} (#{account_id})"
40
+ end
41
+ end
42
+ end
File without changes
@@ -0,0 +1,39 @@
1
+ require 'net/https'
2
+
3
+ module Rugalytics
4
+ class Connection
5
+ def initialize(base_url, args = {})
6
+ @base_url = base_url
7
+ @username = args[:username]
8
+ @password = args[:password]
9
+ end
10
+
11
+ def get(resource, args = nil)
12
+ request(resource, "get", args)
13
+ end
14
+
15
+ def post(resource, args = nil)
16
+ request(resource, "post", args)
17
+ end
18
+
19
+ def request(resource, method = "get", args = nil)
20
+ url = URI.join(@base_url, resource)
21
+ url.query = args.map { |k,v| "%s=%s" % [URI.encode(k.to_s), URI.encode(v.to_s)] }.join("&") if args
22
+
23
+ case method
24
+ when "get"
25
+ req = Net::HTTP::Get.new(url.request_uri)
26
+ when "post"
27
+ req = Net::HTTP::Post.new(url.request_uri)
28
+ end
29
+
30
+ req.basic_auth(@username, @password) if @username && @password
31
+
32
+ http = Net::HTTP.new(url.host, url.port)
33
+ http.use_ssl = (url.port == 443)
34
+
35
+ res = http.start() { |conn| conn.request(req) }
36
+ res.body
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ module Rugalytics
2
+ class Graph
3
+
4
+ attr_reader :name, :points, :from, :to
5
+
6
+ def initialize name, period, points
7
+ @name = name
8
+ @period = period
9
+ dates = @period.split('-')
10
+ unless dates.empty?
11
+ @from = Date.parse(dates[0].strip)
12
+ @to = Date.parse(dates[1].strip)
13
+ end
14
+ @points = points
15
+ end
16
+
17
+ def sum_of_points
18
+ points.sum
19
+ end
20
+
21
+ def points_by_day
22
+ by_day = []
23
+ index = 0
24
+ from.upto(to) do |date|
25
+ by_day << [date, points[index] ]
26
+ index = index.next
27
+ end
28
+ by_day
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module Rugalytics
2
+
3
+ class Item
4
+ include Morph
5
+
6
+ def initialize attributes, values, base_url
7
+ attributes.each_with_index do |attribute, index|
8
+ attribute.sub!('$','dollar')
9
+ attribute.sub!('/',' per ')
10
+ attribute.sub!('.',' ')
11
+ value = values[index]
12
+ morph(attribute, value)
13
+ end
14
+
15
+ self.url = "http://#{base_url}#{url}" if respond_to?(:url)
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,116 @@
1
+ module Rugalytics
2
+ class Profile < ::Google::Base
3
+
4
+ class << self
5
+ def find_all(account_id)
6
+ doc = Hpricot::XML get("https://www.google.com:443/analytics/home/admin?scid=#{account_id}")
7
+ (doc/'select[@name=profile_list] option').inject([]) do |profiles, option|
8
+ profile_id = option['value'].to_i
9
+ profiles << Profile.new(:account_id => account_id, :profile_id => profile_id, :name => option.inner_html) if profile_id > 0
10
+ profiles
11
+ end
12
+ end
13
+
14
+ def find(account_id_or_name, profile_id_or_name=nil)
15
+ profile_id_or_name = account_id_or_name unless profile_id_or_name
16
+ account = Account.find(account_id_or_name)
17
+ account ? account.find_profile(profile_id_or_name) : nil
18
+ end
19
+ end
20
+
21
+ attr_accessor :account_id, :name, :profile_id
22
+
23
+ def initialize(attrs)
24
+ raise ArgumentError, ":profile_id is required" unless attrs.has_key?(:profile_id)
25
+ @account_id = attrs[:account_id] if attrs.has_key?(:account_id)
26
+ @name = attrs[:name] if attrs.has_key?(:name)
27
+ @profile_id = attrs[:profile_id] if attrs.has_key?(:profile_id)
28
+ end
29
+
30
+ def load_report(name, options={})
31
+ report = Rugalytics::Report.new report(options.merge({:report=>name}))
32
+ puts report.attribute_names
33
+ report
34
+ end
35
+
36
+ def report(options={})
37
+ options.reverse_merge!({
38
+ :report => 'Dashboard',
39
+ :from => Time.now.utc - 7.days,
40
+ :to => Time.now.utc,
41
+ :tab => 0,
42
+ :format => FORMAT_CSV,
43
+ :rows => 50,
44
+ :compute => 'average',
45
+ :gdfmt => 'nth_day',
46
+ :view => 0
47
+ })
48
+ options[:from] = ensure_datetime_in_google_format(options[:from])
49
+ options[:to] = ensure_datetime_in_google_format(options[:to])
50
+
51
+ params = {
52
+ :pdr => "#{options[:from]}-#{options[:to]}",
53
+ :rpt => "#{options[:report]}Report",
54
+ :cmp => options[:compute],
55
+ :fmt => options[:format],
56
+ :view => options[:view],
57
+ :tab => options[:tab],
58
+ :trows=> options[:rows],
59
+ :gdfmt=> options[:gdfmt],
60
+ :id => profile_id,
61
+ }
62
+ puts params.inspect
63
+ # https://www.google.com/analytics/reporting/export?fmt=2&id=1712313&pdr=20080504-20080603&cmp=average&&rpt=PageviewsReport
64
+ self.class.get("https://google.com/analytics/reporting/export", :query_hash => params)
65
+ end
66
+
67
+ def pageviews(options={})
68
+ load_report('Pageviews',options).page_views_total
69
+ end
70
+
71
+ def pageviews_by_day(options={})
72
+ load_report('Pageviews',options).page_views_by_day
73
+ end
74
+
75
+ def visits(options={})
76
+ load_report('Visits',options).visits_total
77
+ end
78
+
79
+ def visits_by_day(options={})
80
+ load_report('Visits',options).visits_by_day
81
+ end
82
+
83
+ # takes a Date, Time or String
84
+ def ensure_datetime_in_google_format(time)
85
+ time = Date.parse(time) if time.is_a?(String)
86
+ time.is_a?(Time) || time.is_a?(Date) ? time.strftime('%Y%m%d') : time
87
+ end
88
+
89
+ def to_s
90
+ "#{name} (#{profile_id})"
91
+ end
92
+
93
+ private
94
+ def get_item_summary_by_message(options={})
95
+ raise ArgumentError unless options.has_key?(:message)
96
+ message = options.delete(:message).to_s.capitalize
97
+ response = report(options.merge({:report => 'Dashboard'}))
98
+ doc = Hpricot::XML(response)
99
+ pageviews = (doc/:ItemSummary).detect { |summary| summary.at('Message').inner_html == message }
100
+ pageviews && pageviews.at('SummaryValue') ? pageviews.at('SummaryValue').inner_html.gsub(/\D/, '').to_i : 0
101
+ end
102
+
103
+ def get_serie_by_label(options={})
104
+ raise ArgumentError unless options.has_key?(:label)
105
+ label = options.delete(:label).to_s.capitalize
106
+ response = report(options.merge({:report => 'Dashboard'}))
107
+ doc = Hpricot::XML(response)
108
+ serie = (doc/:Serie).detect { |serie| serie.at('Label').inner_html == label }
109
+ if serie
110
+ (serie/:Point).inject([]) { |collection, point| collection << [Date.parse(point.at('Label').inner_html), point.at('Value').inner_html.to_i] }
111
+ else
112
+ []
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,97 @@
1
+ module Rugalytics
2
+
3
+ class Report
4
+
5
+ include MorphLessMethodMissing
6
+
7
+ attr_reader :base_url, :report_name, :start_date, :end_date
8
+
9
+ def initialize csv=''
10
+ return if csv.empty?
11
+ lines = csv.split("\n")
12
+ set_attributes lines
13
+ handle_graphs lines
14
+ handle_tables lines
15
+ end
16
+
17
+ def attribute_names
18
+ Report.morph_methods.select {|m| m[/[a-z]$/]}.select {|m| send(m.to_sym)}
19
+ end
20
+
21
+ def method_missing symbol, *args
22
+
23
+ if is_writer = symbol.to_s[/=$/]
24
+ morph_method_missing(symbol, *args)
25
+
26
+ elsif symbol.to_s.match /(.*)_(total|by_day)/
27
+ graph = "#{$1}_graph".to_sym
28
+
29
+ if respond_to?(graph)
30
+ $2 == 'total' ? send(graph).sum_of_points : send(graph).points_by_day
31
+ else
32
+ super
33
+ end
34
+
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def set_attributes lines
43
+ @base_url = lines[1]
44
+ @report_name = lines[2].chomp(',')
45
+ dates = lines[3].split(',')
46
+ @start_date = dates[0]
47
+ @end_date = dates[1]
48
+ end
49
+
50
+ def handle_graphs lines
51
+ index = 5
52
+ while index < lines.size
53
+ while (lines[index][/^# Graph/].nil? || lines[index].strip.size == 0)
54
+ index = index.next
55
+ return if index == lines.size
56
+ end
57
+ index = index + 2
58
+ period = lines[index]
59
+ index = index.next
60
+ name = lines[index]
61
+ index = index.next
62
+
63
+ points = []
64
+ while (point = lines[index]) && point.strip.size > 0
65
+ points << point.tr('",','').to_i
66
+ index = index.next
67
+ end
68
+
69
+ graph = Graph.new name, period, points
70
+ morph("#{name} graph", graph)
71
+ end
72
+ end
73
+
74
+ def handle_tables lines
75
+ index = 5
76
+ while index < lines.size
77
+ while (lines[index][/^# .*Table/].nil? || lines[index].strip.size == 0)
78
+ index = index.next
79
+ return if index == lines.size
80
+ end
81
+ type = lines[index][/^# (.*)MiniTable/,1]
82
+ index = index + 2
83
+ attributes = lines[index].split(',')
84
+ index = index.next
85
+
86
+ items = []
87
+ items_attribute = (type && type.size > 0) ? "#{type.gsub(/([a-z])([A-Z])/, '\1_\2').downcase}_items" : 'items'
88
+ morph(items_attribute, items)
89
+
90
+ while (values = lines[index]) && values[/^# -/].nil? && values.strip.size > 0
91
+ items << Item.new(attributes, values.split(','), base_url)
92
+ index = index.next
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end