Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
joshovest committed Oct 4, 2012
0 parents commit 461dc98
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
test/config.yml
*.gem
.bundle
Gemfile.lock
pkg/*
.DS_STORE
4 changes: 4 additions & 0 deletions Gemfile
@@ -0,0 +1,4 @@
source "http://rubygems.org"

# Specify your gem's dependencies in romniture.gemspec
gemspec
22 changes: 22 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,22 @@
Copyright (c) 2012 Josh West

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
56 changes: 56 additions & 0 deletions README.md
@@ -0,0 +1,56 @@
# romniture
To be pronounced...RAWWWRROMNITURE

## what is it
romniture is a minimal Ruby wrapper to [Omniture's REST API](http://developer.omniture.com). It follows a design policy similar to that of [sucker](https://rubygems.org/gems/sucker) built for Amazon's API.

Omniture's API is closed, you have to be a paying customer in order to access the data.

## installation
[sudo] gem install romniture

## initialization and authentication
romniture requires you supply the `username`, `shared_secret` and `environment` which you can access within the Company > Web Services section of the Admin Console. The environment you'll use to connect to Omniture's API depends on which data center they're using to store your traffic data and will be one of:

* San Jose (https://api.omniture.com/admin/1.3/rest/)
* Dallas (https://api2.omniture.com/admin/1.3/rest/)
* London (https://api3.omniture.com/admin/1.3/rest/)
* San Jose Beta (https://beta-api.omniture.com/admin/1.3/rest/)
* Dallas (beta) (https://beta-api2.omniture.com/admin/1.3/rest/)
* Sandbox (https://api-sbx1.omniture.com/admin/1.3/rest/)

Here's an example of initializing with a few configuration options.

client = ROmniture::Client.new(
username,
shared_secret,
:san_jose,
:verify_mode => nil # Optionaly change the ssl verify mode.
:log => false, # Optionally turn off logging if it ticks you off
:wait_time => 1 # Amount of seconds to wait in between pinging
# Omniture's servers to see if a report is done processing (BEWARE OF TOKENS!)
)

## usage
There are only two core methods for the client which doesn't try to "over architect a spaghetti API":

* `get_report` - used to...while get reports and
* `request` - more generic used to make any kind of request

For reference, I'd recommend keeping [Omniture's Developer Portal](http://developer.omniture.com) open as you code . It's not the easiest to navigate but most of what you need is there.

The response returned by either of these requests Ruby (parsed JSON).

## examples
# Find all the company report suites
client.request('Company.GetReportSuites')

# Get an overtime report
client.get_report "Report.QueueOvertime", {
"reportDescription" => {
"reportSuiteID" => "#{@config["report_suite_id"]}",
"dateFrom" => "2011-01-01",
"dateTo" => "2011-01-10",
"metrics" => [{"id" => "pageviews"}]
}
}
9 changes: 9 additions & 0 deletions Rakefile
@@ -0,0 +1,9 @@
require "rubygems"
require "bundler/gem_tasks"

desc "Run all tests in /test"
task :test do
Dir["test/**/*_test.rb"].each do |test_path|
system "ruby #{test_path}"
end
end
16 changes: 16 additions & 0 deletions lib/romniture.rb
@@ -0,0 +1,16 @@
require "romniture/version"
require "romniture/client"
require "romniture/exceptions"

require "rubygems"

require "logger"
require "httpi"
require "digest/md5"
require "digest/sha1"
require "base64"
require "json"

module ROmniture

end
153 changes: 153 additions & 0 deletions lib/romniture/client.rb
@@ -0,0 +1,153 @@
module ROmniture

class Client

DEFAULT_REPORT_WAIT_TIME = 0.25

ENVIRONMENTS = {
:san_jose => "https://api.omniture.com/admin/1.3/rest/",
:dallas => "https://api2.omniture.com/admin/1.3/rest/",
:london => "https://api3.omniture.com/admin/1.3/rest/",
:san_jose_beta => "https://beta-api.omniture.com/admin/1.3/rest/",
:dallas_beta => "https://beta-api2.omniture.com/admin/1.3/rest/",
:sandbox => "https://api-sbx1.omniture.com/admin/1.3/rest/"
}

def initialize(username, shared_secret, environment, options={})
@username = username
@shared_secret = shared_secret
@environment = environment.is_a?(Symbol) ? ENVIRONMENTS[environment] : environment.to_s

@wait_time = options[:wait_time] ? options[:wait_time] : DEFAULT_REPORT_WAIT_TIME
@log = options[:log] ? options[:log] : false
@verify_mode = options[:verify_mode] ? options[:verify_mode] : false
HTTPI.log = false
end

def request(method, parameters = {})
response = send_request(method, parameters)

parsed_response = nil
begin
parsed_response = JSON.parse(response.body)
rescue
parsed_response = response.body
end

parsed_response
end

def get_report(method, report_description)
response = send_request(method, report_description)

json = JSON.parse response.body
if json["status"] == "queued"
log(Logger::INFO, "Report with ID (" + json["reportID"].to_s + ") queued. Now fetching report...")
return get_queued_report json["reportID"]
else
log(Logger::ERROR, "Could not queue report. Omniture returned with error:\n#{response.body}")
raise "Could not queue report. Omniture returned with error:\n#{response.body}"
end
end

attr_writer :log

def log?
@log != false
end

def logger
@logger ||= ::Logger.new(STDOUT)
end

def log_level
@log_level ||= ::Logger::INFO
end

def log(*args)
level = args.first.is_a?(Numeric) || args.first.is_a?(Symbol) ? args.shift : log_level
logger.log(level, args.join(" ")) if log?
end

private

def send_request(method, data)
log(Logger::INFO, "Requesting #{method}...")
generate_nonce

log(Logger::INFO, "Created new nonce: #{@password}")

request = HTTPI::Request.new

if @verify_mode
request.auth.ssl.verify_mode = @verify_mode
end

request.url = @environment + "?method=#{method}"
request.headers = request_headers
request.body = data.to_json

response = HTTPI.post(request)

if response.code >= 400
log(:error, "Request failed and returned with response code: #{response.code}\n\n#{response.body}")
raise "Request failed and returned with response code: #{response.code}\n\n#{response.body}"
end

log(Logger::INFO, "Server responded with response code #{response.code}.")

response
end

def generate_nonce
@nonce = Digest::MD5.new.hexdigest(rand().to_s)
@created = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
combined_string = @nonce + @created + @shared_secret
sha1_string = Digest::SHA1.new.hexdigest(combined_string)
@password = Base64.encode64(sha1_string).to_s.chomp("\n")
end

def request_headers
{
"X-WSSE" => "UsernameToken Username=\"#{@username}\", PasswordDigest=\"#{@password}\", Nonce=\"#{@nonce}\", Created=\"#{@created}\""
}
end

def get_queued_report(report_id)
done = false
error = false
status = nil
start_time = Time.now
end_time = nil

begin
response = send_request("Report.GetStatus", {"reportID" => "#{report_id}"})
log(Logger::INFO, "Checking on status of report #{report_id}...")

json = JSON.parse(response.body)
status = json["status"]

if status == "done"
done = true
elsif status == "failed"
error = true
end

sleep @wait_time if !done && !error
end while !done && !error

if error
msg = "Unable to get data for report #{report_id}. Status: #{status}. Error Code: #{json["error_code"]}. #{json["error_msg"]}."
log(:error, msg)
raise ROmniture::Exceptions::OmnitureReportException.new(json), msg
end

response = send_request("Report.GetReport", {"reportID" => "#{report_id}"})

end_time = Time.now
log(Logger::INFO, "Report with ID #{report_id} has finished processing in #{((end_time - start_time)*1000).to_i} ms")

JSON.parse(response.body)
end
end
end
13 changes: 13 additions & 0 deletions lib/romniture/exceptions.rb
@@ -0,0 +1,13 @@
module ROmniture
module Exceptions

class OmnitureReportException < StandardError
attr_reader :data
def initialize(data)
@data = data
super
end
end

end
end
3 changes: 3 additions & 0 deletions lib/romniture/version.rb
@@ -0,0 +1,3 @@
module ROmniture
VERSION = "0.0.4"
end
23 changes: 23 additions & 0 deletions romniture.gemspec
@@ -0,0 +1,23 @@
$:.push File.expand_path("../lib", __FILE__)
require "romniture/version"

Gem::Specification.new do |s|
s.name = "romniture"
s.version = ROmniture::VERSION
s.authors = ["Josh West"]
s.email = ["joshovest@gmail.com"]
s.homepage = "http://github.com/joshovest/romniture"
s.summary = "Use Omniture's REST API with ease."
s.description = "A library that allows access to Omniture's REST API libraries (developer.omniture.com)"

s.rubyforge_project = s.name

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]

s.add_runtime_dependency("httpi")
s.add_runtime_dependency("json")

end
58 changes: 58 additions & 0 deletions test/client_test.rb
@@ -0,0 +1,58 @@
require 'rubygems'
require 'test/unit'
require 'yaml'

require 'romniture'

class ClientTest < Test::Unit::TestCase

def setup
config = YAML::load(File.open("test/config.yml"))
@config = config["omniture"]

@client = ROmniture::Client.new(
@config["username"],
@config["shared_secret"],
@config["environment"],
:verify_mode => @config['verify_mode'],
:wait_time => @config["wait_time"]
)
end

def test_simple_request
response = @client.request('Company.GetReportSuites')

assert_instance_of Hash, response, "Returned object is not a hash."
assert(response.has_key?("report_suites"), "Returned hash does not contain any report suites.")
end

def test_report_request
response = @client.get_report "Report.QueueOvertime", {
"reportDescription" => {
"reportSuiteID" => "#{@config["report_suite_id"]}",
"dateFrom" => "2011-01-01",
"dateTo" => "2011-01-10",
"metrics" => [{"id" => "pageviews"}]
}
}

assert_instance_of Hash, response, "Returned object is not a hash."
assert(response["report"].has_key?("data"), "Returned hash has no data!")
end

def test_a_bad_request
# Bad request, mixing commerce and traffic variables
assert_raise(ROmniture::Exceptions::OmnitureReportException) do
response = @client.get_report("Report.QueueTrended", {
"reportDescription" => {
"reportSuiteID" => @config["report_suite_id"],
"dateFrom" => "2011-01-01",
"dateTo" => "2011-01-11",
"metrics" => [{"id" => "pageviews"}, {"id" => "event11"}],
"elements" => [{"id" => "siteSection"}]
}
})
end
end

end

0 comments on commit 461dc98

Please sign in to comment.