rugalytics 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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