Skip to content

Commit

Permalink
Initial implementation of API
Browse files Browse the repository at this point in the history
  • Loading branch information
tpitale committed Jan 20, 2009
1 parent 1ec223d commit 57596c3
Show file tree
Hide file tree
Showing 17 changed files with 689 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
/pkg/
Empty file added History.txt
Empty file.
17 changes: 17 additions & 0 deletions Manifest.txt
@@ -0,0 +1,17 @@
History.txt
Manifest.txt
README.txt
Rakefile
lib/garb.rb
lib/garb/account.rb
lib/garb/profile.rb
lib/garb/report.rb
lib/garb/request.rb
lib/garb/session.rb
test/account_test.rb
test/garb_test.rb
test/profile_test.rb
test/report_test.rb
test/request_test.rb
test/session_test.rb
test/test_helper.rb
26 changes: 26 additions & 0 deletions Rakefile
@@ -0,0 +1,26 @@
# Look in the tasks/setup.rb file for the various options that can be
# configured in this Rakefile. The .rake files in the tasks directory
# are where the options are used.

begin
require 'bones'
Bones.setup
rescue LoadError
load 'tasks/setup.rb'
end

ensure_in_path 'lib'
require 'garb'

task :default => 'test'

PROJ.name = 'garb'
PROJ.authors = ['Tony Pitale','Justin Marney']
PROJ.email = 'tony.pitale@viget.com'
PROJ.url = 'http://github.com/vigetlabs/garb'
PROJ.version = Garb::VERSION
PROJ.rubyforge.name = 'garb'
PROJ.test.files = FileList['test/**/*_test.rb']
PROJ.spec.opts << '--color'

# EOF
61 changes: 61 additions & 0 deletions lib/garb.rb
@@ -0,0 +1,61 @@
$:.unshift File.expand_path(File.dirname(__FILE__))

require 'net/http'
require 'net/https'
require 'rubygems'
require 'atom'

require 'garb/request'
require 'garb/session'
require 'garb/account'
require 'garb/profile'
require 'garb/report'

module Garb
# :stopdoc:
GA = "http://schemas.google.com/analytics/2008"

VERSION = '0.0.1'
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
# :startdoc:

# Returns the version string for the library.
#
def self.version
VERSION
end

# Returns the library path for the module. If any arguments are given,
# they will be joined to the end of the libray path using
# <tt>File.join</tt>.
#
def self.libpath( *args )
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
end

# Returns the lpath for the module. If any arguments are given,
# they will be joined to the end of the path using
# <tt>File.join</tt>.
#
def self.path( *args )
args.empty? ? PATH : ::File.join(PATH, args.flatten)
end

# Utility method used to rquire all files ending in .rb that lie in the
# directory below this file that has the same name as the filename passed
# in. Optionally, a specific _directory_ name can be passed in such that
# the _filename_ does not have to be equivalent to the directory.
#
def self.require_all_libs_relative_to( fname, dir = nil )
dir ||= ::File.basename(fname, '.*')
search_me = ::File.expand_path(
::File.join(::File.dirname(fname), dir, '*', '*.rb'))

Dir.glob(search_me).sort.each {|rb| require rb}
end
end # module Garb

Garb.require_all_libs_relative_to(__FILE__)

# EOF
26 changes: 26 additions & 0 deletions lib/garb/account.rb
@@ -0,0 +1,26 @@
module Garb
class Account
URL = "https://www.google.com/analytics/feeds/accounts/"

attr_reader :profiles, :session

def initialize(session)
@session = session
@profiles = []
end

def request
@request = Request.new(URL+session.email)
@request.session = session
@request
end

def all
feed = request.get
feed.each_entry do |entry|
@profiles << Profile.new(entry)
end
@profiles
end
end
end
10 changes: 10 additions & 0 deletions lib/garb/profile.rb
@@ -0,0 +1,10 @@
module Garb
class Profile
attr_reader :tableId, :title

def initialize(entry)
@tableId = Report.property_value(entry, :tableId)
@title = entry.title
end
end
end
87 changes: 87 additions & 0 deletions lib/garb/report.rb
@@ -0,0 +1,87 @@
module Garb
class Report
MONTH = 2592000
URL = "https://www.google.com/analytics/feeds/data"

attr_accessor :metrics, :dimensions, :sort,
:start_date, :max_results,
:end_date, :profile, :session

def self.element_id(property_name)
property_name.is_a?(Symbol) ? "ga:#{property_name}" : property_name
end

def self.property_value(entry, property_name)
entry[GA, property_name.to_s].first
end

def self.property_values(entry, property_names)
property_names.inject({}) do |hash, property_name|
hash.merge({property_name => property_value(entry, property_name)})
end
end

def self.format_time(t)
t.strftime('%Y-%m-%d')
end

def initialize(profile, session, opts={})
@profile = profile
@session = session
@metrics = opts.fetch(:metrics, [])
@dimensions = opts.fetch(:dimensions, [])
@sort = opts.fetch(:sort, [])
@start_date = opts.fetch(:start_date, Time.now - MONTH)
@end_date = opts.fetch(:end_date, Time.now)
yield self if block_given?
end

def metric_params
{'metrics' => parameterize(metrics)}
end

def dimension_params
{'dimensions' => parameterize(dimensions)}
end

def sort_params
{'sort' => parameterize(sort)}
end

def page_params
max_results.nil? ? {} : {'max-results' => max_results}
end

def default_params
{'ids' => profile.tableId,
'start-date' => self.class.format_time(start_date),
'end-date' => self.class.format_time(end_date)}
end

def params
[metric_params, dimension_params, sort_params, page_params].inject(default_params) do |p, i|
p.merge(i)
end
end

def request
@request = Request.new(URL, params)
@request.session = session
@request
end

def all
entries = []
feed = request.get
feed.each_entry do |entry|
entries << self.class.property_values(entry, metrics+dimensions)
end
entries
end

private
def parameterize(coll)
coll.collect{|prop| self.class.element_id(prop)}.join(',')
end
end
end
41 changes: 41 additions & 0 deletions lib/garb/request.rb
@@ -0,0 +1,41 @@
module Garb
class Request

attr_accessor :session

def initialize(url, parameters={})
@url = URI.parse(url)
@parameters = parameters
end

def http
http = Net::HTTP.new(@url.host, @url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http
end

def post
request = Net::HTTP::Post.new(@url.path)
request.set_form_data(@parameters)
self.http.request(request)
end

def get
request = Net::HTTP::Get.new(@url.path+url_parameters)
request['Authorization'] = "GoogleLogin auth=#{@session.auth_token}"
response = self.http.request(request)
raise response.body.inspect
begin
Atom::Feed.load_feed response.body if response
rescue ArgumentError
puts response.body.inspect
end
end

def url_parameters
@parameters.empty? ? "" : "?" + @parameters.map{|k,v| "#{k}=#{v}"}.join("&")
end

end
end
34 changes: 34 additions & 0 deletions lib/garb/session.rb
@@ -0,0 +1,34 @@
module Garb
class Session

attr_accessor :auth_token, :email

URL = 'https://www.google.com/accounts/ClientLogin'

def initialize(email, password)
@email = email
@params = self.class.default_params.merge({'Email' => email, 'Passwd' => password})
end

def self.default_params
{
'Email' => '',
'Passwd' => '',
'accountType' => 'HOSTED_OR_GOOGLE',
'service' => 'analytics',
'source' => 'vigetLabs-garb-001'
}
end

def logged_in?
!(auth_token.nil? || auth_token == '')
end

def get_auth_token
request = Request.new(URL, @params)
response = request.post
self.auth_token = response.body.match(/Auth=(.*)/)[1] || nil
logged_in?
end
end
end
36 changes: 36 additions & 0 deletions test/account_test.rb
@@ -0,0 +1,36 @@
require File.join(File.dirname(__FILE__), 'test_helper')

module Garb
class AccountTest < Test::Unit::TestCase
context "An instance of an Account" do
setup do
session = stub do |s|
s.stubs(:email).returns('ga@example.com')
end

@account = Account.new(session)
end

should "create a request to be used from the URL and session email" do
request_stub = stub(:session=)
Request.stubs(:new).with(Account::URL+'ga@example.com').returns(request_stub)
assert_equal request_stub, @account.request
end

should "get a list of all profiles for an account" do
feed_stub = stub do |s|
s.stubs(:each_entry).yields('entry')
end

Profile.stubs(:new).with('entry').returns('profile')
@account.stubs(:request).returns(stub(:get => feed_stub))

assert_equal ['profile'], @account.all
end

should "store an array of profiles for an account" do
assert_equal [], @account.profiles
end
end
end
end
9 changes: 9 additions & 0 deletions test/garb_test.rb
@@ -0,0 +1,9 @@
require File.join(File.dirname(__FILE__), 'test_helper')

class GarbTest < Test::Unit::TestCase
context "A green egg" do
should "be served with ham" do
assert true
end
end
end
19 changes: 19 additions & 0 deletions test/profile_test.rb
@@ -0,0 +1,19 @@
require File.join(File.dirname(__FILE__), 'test_helper')

module Garb
class ProfileTest < Test::Unit::TestCase
context "An instance of a Profile" do
should "retain the tableId and title from an entry" do
entry = stub do |s|
s.stubs(:title).returns('entry')
end

Report.stubs(:property_value).with(entry, :tableId).returns('ga:1234')

profile = Profile.new(entry)
assert_equal 'entry', profile.title
assert_equal 'ga:1234', profile.tableId
end
end
end
end

0 comments on commit 57596c3

Please sign in to comment.