google-analytics 0.1.0

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.
@@ -0,0 +1 @@
1
+ v0.1.0 Nothing here yet
data/LICENSE ADDED
File without changes
@@ -0,0 +1,17 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.textile
5
+ Rakefile
6
+ google-analytics.gemspec
7
+ lib/google_analytics.rb
8
+ lib/google_analytics/account.rb
9
+ lib/google_analytics/client.rb
10
+ lib/google_analytics/report.rb
11
+ lib/google_analytics/user.rb
12
+ test.rb
13
+ test/google_analytics/account_test.rb
14
+ test/google_analytics/client_test.rb
15
+ test/google_analytics/report_test.rb
16
+ test/google_analytics/user_test.rb
17
+ test/test_helper.rb
@@ -0,0 +1,3 @@
1
+ h1. Google Analytics API for Ruby
2
+
3
+ Does what it says on the tin.
@@ -0,0 +1,12 @@
1
+ require 'echoe'
2
+ require 'fileutils'
3
+
4
+ Echoe.new("google-analytics") do |p|
5
+ p.author = "Dan Webb"
6
+ p.email = 'dan@danwebb.net'
7
+ p.summary = "Wrapper around the Google Analytics API"
8
+ p.runtime_dependencies = ['nokogiri']
9
+ p.development_dependencies = ['shoulda', 'fakeweb']
10
+ p.retain_gemspec = true
11
+ p.rcov_options = "--exclude 'gems/*'"
12
+ end
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{google-analytics}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Dan Webb"]
9
+ s.date = %q{2009-10-12}
10
+ s.description = %q{Wrapper around the Google Analytics API}
11
+ s.email = %q{dan@danwebb.net}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.textile", "lib/google_analytics.rb", "lib/google_analytics/account.rb", "lib/google_analytics/client.rb", "lib/google_analytics/report.rb", "lib/google_analytics/user.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.textile", "Rakefile", "google-analytics.gemspec", "lib/google_analytics.rb", "lib/google_analytics/account.rb", "lib/google_analytics/client.rb", "lib/google_analytics/report.rb", "lib/google_analytics/user.rb", "test.rb", "test/google_analytics/account_test.rb", "test/google_analytics/client_test.rb", "test/google_analytics/report_test.rb", "test/google_analytics/user_test.rb", "test/test_helper.rb"]
14
+ s.homepage = %q{}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Google-analytics", "--main", "README.textile"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{google-analytics}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Wrapper around the Google Analytics API}
20
+ s.test_files = ["test/google_analytics/account_test.rb", "test/google_analytics/client_test.rb", "test/google_analytics/report_test.rb", "test/google_analytics/user_test.rb", "test/test_helper.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
28
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
29
+ s.add_development_dependency(%q<fakeweb>, [">= 0"])
30
+ else
31
+ s.add_dependency(%q<nokogiri>, [">= 0"])
32
+ s.add_dependency(%q<shoulda>, [">= 0"])
33
+ s.add_dependency(%q<fakeweb>, [">= 0"])
34
+ end
35
+ else
36
+ s.add_dependency(%q<nokogiri>, [">= 0"])
37
+ s.add_dependency(%q<shoulda>, [">= 0"])
38
+ s.add_dependency(%q<fakeweb>, [">= 0"])
39
+ end
40
+ end
@@ -0,0 +1,9 @@
1
+ module GoogleAnalytics
2
+ autoload :Client, 'google_analytics/client'
3
+ autoload :User, 'google_analytics/user'
4
+ autoload :Account, 'google_analytics/account'
5
+ autoload :Profile, 'google_analytics/profile'
6
+ autoload :Report, 'google_analytics/report'
7
+
8
+ DXP_NAMESPACE = 'http://schemas.google.com/analytics/2009'
9
+ end
@@ -0,0 +1,25 @@
1
+ module GoogleAnalytics
2
+
3
+ class Account
4
+ attr_reader :url, :title, :id, :profile_id, :client
5
+
6
+ def self.from_node(client, node)
7
+ new client,
8
+ node.xpath('.//xmlns:id').first.content,
9
+ node.xpath('.//dxp:property[@name="ga:accountName"]', 'dxp' => DXP_NAMESPACE).first.attributes['value'].to_s,
10
+ node.xpath('.//dxp:property[@name="ga:accountId"]', 'dxp' => DXP_NAMESPACE).first.attributes['value'].to_s,
11
+ node.xpath('.//dxp:property[@name="ga:profileId"]', 'dxp' => DXP_NAMESPACE).first.attributes['value'].to_s
12
+ end
13
+
14
+ def initialize(client, url, title, id, profile_id)
15
+ @client, @url, @title = client, url, title
16
+ @id, @profile_id = id, profile_id
17
+ end
18
+
19
+ def report(options={})
20
+ Report.new(self, options)
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,98 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'nokogiri'
5
+
6
+ module GoogleAnalytics
7
+
8
+ AUTH_URL = 'https://www.google.com/accounts/ClientLogin'
9
+ BASE_URL = 'https://www.google.com/analytics'
10
+
11
+ class NotAuthorized < StandardError; end
12
+ class ResponseError < StandardError; end
13
+
14
+ class Client
15
+
16
+ def initialize(username, password)
17
+ @username, @password = username, password
18
+ end
19
+
20
+ def login!
21
+ data = {
22
+ :accountType => 'GOOGLE_OR_HOSTED',
23
+ :service => 'analytics',
24
+ :Email => @username,
25
+ :Passwd => @password
26
+ }
27
+
28
+ begin
29
+ resp = request(URI.parse(AUTH_URL), :post, data)
30
+
31
+ if resp =~ /^Auth\=(.+)$/
32
+ @auth = $1
33
+ else
34
+ raise NotAuthorized, 'you cannot access this google account'
35
+ end
36
+ rescue GoogleAnalytics::ResponseError
37
+ raise NotAuthorized, 'you cannot access this google account'
38
+ end
39
+ end
40
+
41
+ def get(path, args=nil)
42
+ api_request(path, :get, args)
43
+ end
44
+
45
+ def post(path, args=nil)
46
+ api_request(path, :post, args)
47
+ end
48
+
49
+ def api_request(path, method, args)
50
+ login! unless @auth
51
+ url = URI.parse([BASE_URL, path].join)
52
+
53
+ resp = request(url, method, args)
54
+
55
+ Nokogiri::XML(resp)
56
+ end
57
+
58
+ def request(url, method=:get, data=nil)
59
+ case method
60
+ when :get
61
+ url.query = encode(data) if data
62
+ req = Net::HTTP::Get.new(url.request_uri)
63
+ when :post
64
+ req = Net::HTTP::Post.new(url.request_uri)
65
+ req.body = encode(data) if data
66
+ req.content_type = 'application/x-www-form-urlencoded'
67
+ end
68
+
69
+ req['Authorization'] = "GoogleLogin auth=#{@auth}" if @auth
70
+
71
+ puts url.to_s
72
+
73
+ http = Net::HTTP.new(url.host, url.port)
74
+ http.use_ssl = (url.port == 443)
75
+
76
+ res = http.start() { |conn| conn.request(req) }
77
+
78
+ raise ResponseError, "#{res.code} #{res.message}: #{res.body}" unless res.code == '200'
79
+
80
+ res.body
81
+ end
82
+
83
+ protected
84
+
85
+ def encode(data)
86
+ data.map do |k,v|
87
+ value = v.is_a?(Array) ? v.join(',') : v.to_s
88
+ ("%s=%s" % [uri_encode(k), uri_encode(value)]) unless value.empty?
89
+ end.compact.join("&")
90
+ end
91
+
92
+ def uri_encode(item)
93
+ encoded = URI.encode(item.to_s)
94
+ encoded.gsub(/\=/, '%3D')
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,93 @@
1
+ module GoogleAnalytics
2
+ class Report
3
+
4
+ def initialize(account, options={})
5
+ @account = account
6
+ @options = {
7
+ :ids => "ga:#{account.profile_id}",
8
+ :metrics => ga_prefix(options.delete(:metrics)),
9
+ :dimensions => ga_prefix(options.delete(:dimensions)),
10
+ :'start-date' => date_format(options.delete(:start) || one_month_ago),
11
+ :'end-date' => date_format(options.delete(:end) || Time.now)
12
+ }.merge(options)
13
+
14
+ if filters = options.delete(:filters)
15
+ @options[:filters] = ga_prefix(filters)
16
+ end
17
+
18
+ if sort = options.delete(:sort)
19
+ @options[:sort] = ga_prefix(sort)
20
+ end
21
+ end
22
+
23
+ def each
24
+ page = 1
25
+ items = rows(page)
26
+
27
+ while !items.empty?
28
+ yield items.pop
29
+
30
+ items = rows(page+=1) if items.empty?
31
+ end
32
+ end
33
+
34
+ def rows(page=1, per_page=10000)
35
+ parse_rows(get(
36
+ :"start-index" => start_index(page, per_page),
37
+ :'max-results' => per_page
38
+ ))
39
+ end
40
+
41
+ def get(options={})
42
+ @account.client.get('/feeds/data', @options.merge(options))
43
+ end
44
+
45
+ protected
46
+
47
+ def start_index(page, per_page)
48
+ ((page - 1) * per_page) + 1
49
+ end
50
+
51
+ def parse_rows(data)
52
+ rows = []
53
+ data.xpath('.//xmlns:entry').each do |node|
54
+ row = {}
55
+
56
+ node.xpath('./dxp:dimension').each do |dim|
57
+ row[strip_prefix(dim.attributes['name'])] = dim.attributes['value'].to_s
58
+ end
59
+
60
+ node.xpath('./dxp:metric').each do |dim|
61
+ row[strip_prefix(dim.attributes['name'])] = dim.attributes['value'].to_s
62
+ end
63
+
64
+ rows << row
65
+ end
66
+ return rows
67
+ end
68
+
69
+ def strip_prefix(name)
70
+ name.to_s.gsub(/^ga\:/, '')
71
+ end
72
+
73
+ def ga_prefix(data)
74
+ if data.is_a?(Array)
75
+ data.collect { |d| d.gsub(/(-?)(\w+)/, "\\1ga:\\2") }
76
+ elsif !data.nil?
77
+ d.gsub(/(-?)(\w+)/, "\\1ga:\\2")
78
+ end
79
+ end
80
+
81
+ def date_format(date)
82
+ if date.nil?
83
+ nil
84
+ else
85
+ date.strftime('%Y-%m-%d')
86
+ end
87
+ end
88
+
89
+ def one_month_ago
90
+ Time.now - (28 * 24 * 60 * 60)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ module GoogleAnalytics
2
+
3
+ class User
4
+
5
+ def initialize(username, password)
6
+ @client = Client.new username, password
7
+ end
8
+
9
+ def accounts
10
+ return @accounts if @accounts
11
+ xml = @client.get('/feeds/accounts/default')
12
+ @accounts = xml.xpath('//xmlns:entry').collect do |account_node|
13
+ Account.from_node(@client, account_node)
14
+ end
15
+ end
16
+
17
+ def account(id)
18
+ accounts.find do |account|
19
+ account if account.id == id
20
+ end
21
+ end
22
+
23
+ def valid?
24
+ @client.login!
25
+ true
26
+ rescue NotAuthorized
27
+ false
28
+ end
29
+ end
30
+
31
+ end
data/test.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'google_analytics'
3
+
4
+ include GoogleAnalytics
5
+
6
+ u = User.new 'dan@danwebb.net', 'poortheory'
7
+
8
+ r = u.accounts[1].report :dimensions => ['keyword'],
9
+ :metrics => ['visits', 'bounces', 'newVisits', 'timeOnSite']
10
+
11
+ puts r.rows(1).inspect
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class AccountTest < Test::Unit::TestCase
4
+ end
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class ClientTest < Test::Unit::TestCase
4
+
5
+ context 'given an instance of Client' do
6
+ setup do
7
+ @client = GoogleAnalytics::Client.new('user', 'password')
8
+ end
9
+
10
+ should 'turn a hash into a url encoded string when encode called' do
11
+ res = @client.send :encode, { :thing => 12, :thong => 'something with spaces' }
12
+ assert_match /thing\=12/, res
13
+ assert_match /thong\=something\%20with\%20spaces/, res
14
+ end
15
+
16
+ should 'should comma seperate arrays when encode called' do
17
+ res = @client.send :encode, { :thong => [:blurg, :blargh] }
18
+ assert_match /thong\=blurg,blargh/, res
19
+ end
20
+
21
+ should 'send request to correct url with username and password' do
22
+ @client.expects(:request).with(
23
+ URI.parse(GoogleAnalytics::AUTH_URL),
24
+ :post,
25
+ {
26
+ :Email => 'user', :Passwd => 'password',
27
+ :accountType => 'GOOGLE_OR_HOSTED', :service => 'analytics'
28
+ }
29
+ ).returns('Auth=thing')
30
+
31
+ @client.login!
32
+ end
33
+
34
+ should 'raise not authorized if failed to log in' do
35
+ @client.expects(:request).with(
36
+ URI.parse(GoogleAnalytics::AUTH_URL),
37
+ :post,
38
+ {
39
+ :Email => 'user', :Passwd => 'password',
40
+ :accountType => 'GOOGLE_OR_HOSTED', :service => 'analytics'
41
+ }
42
+ ).returns('fail')
43
+
44
+ assert_raises GoogleAnalytics::NotAuthorized do
45
+ @client.login!
46
+ end
47
+ end
48
+
49
+ should 'login if auth is not set before api_request' do
50
+ @client.instance_variable_set(:@auth, nil)
51
+ @client.expects(:login!)
52
+ @client.expects(:request).returns('')
53
+
54
+ @client.api_request('/', :get, {})
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class ReportTest < Test::Unit::TestCase
4
+ end
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class UserTest < Test::Unit::TestCase
4
+ end
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '/../lib'))
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'shoulda'
6
+ require 'mocha'
7
+ require 'fakeweb'
8
+ require 'google_analytics'
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google-analytics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Webb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-12 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nokogiri
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: shoulda
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: fakeweb
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Wrapper around the Google Analytics API
46
+ email: dan@danwebb.net
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - CHANGELOG
53
+ - LICENSE
54
+ - README.textile
55
+ - lib/google_analytics.rb
56
+ - lib/google_analytics/account.rb
57
+ - lib/google_analytics/client.rb
58
+ - lib/google_analytics/report.rb
59
+ - lib/google_analytics/user.rb
60
+ files:
61
+ - CHANGELOG
62
+ - LICENSE
63
+ - Manifest
64
+ - README.textile
65
+ - Rakefile
66
+ - google-analytics.gemspec
67
+ - lib/google_analytics.rb
68
+ - lib/google_analytics/account.rb
69
+ - lib/google_analytics/client.rb
70
+ - lib/google_analytics/report.rb
71
+ - lib/google_analytics/user.rb
72
+ - test.rb
73
+ - test/google_analytics/account_test.rb
74
+ - test/google_analytics/client_test.rb
75
+ - test/google_analytics/report_test.rb
76
+ - test/google_analytics/user_test.rb
77
+ - test/test_helper.rb
78
+ has_rdoc: true
79
+ homepage: ""
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --line-numbers
85
+ - --inline-source
86
+ - --title
87
+ - Google-analytics
88
+ - --main
89
+ - README.textile
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: "0"
97
+ version:
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: "1.2"
103
+ version:
104
+ requirements: []
105
+
106
+ rubyforge_project: google-analytics
107
+ rubygems_version: 1.3.5
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Wrapper around the Google Analytics API
111
+ test_files:
112
+ - test/google_analytics/account_test.rb
113
+ - test/google_analytics/client_test.rb
114
+ - test/google_analytics/report_test.rb
115
+ - test/google_analytics/user_test.rb
116
+ - test/test_helper.rb