Permalink
Browse files

Initial Commit

  • Loading branch information...
0 parents commit f7d029efa8d9e8e7840584bb950e04048fe94042 Simon Reed committed Aug 31, 2011
@@ -0,0 +1,3 @@
+pkg/*
+*.gem
+.bundle
@@ -0,0 +1,4 @@
+source :gemcutter
+
+# Specify your gem's dependencies in pathways.gemspec
+gemspec
@@ -0,0 +1,2 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
@@ -0,0 +1,12 @@
+#!/usr/bin/env ruby
+
+$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+begin
+ require 'vegas'
+rescue LoadError
+ require 'rubygems'
+ require 'vegas'
+end
+require 'pathways'
+
+Vegas::Runner.new(Pathways::Server, 'pathways-web')
@@ -0,0 +1,12 @@
+#!/usr/bin/env ruby
+
+$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+begin
+ require 'vegas'
+rescue LoadError
+ require 'rubygems'
+ require 'vegas'
+end
+require 'pathways'
+
+Pathways::Parser.execute
@@ -0,0 +1,12 @@
+module Pathways
+ require 'mongo_mapper'
+ require "pathways/tracker"
+ require "pathways/filter"
+ require "pathways/parser"
+ require "pathways/server"
+ require "pathways/session"
+ require "pathways/visit"
+
+ MongoMapper.connection = Mongo::Connection.new('localhost')
+ MongoMapper.database = 'pathways'
+end
@@ -0,0 +1,39 @@
+module Pathways
+ class Filter
+ def self.map
+ <<-JS
+ function(){
+ this.visits.forEach(function(visit){
+ emit(visit.url, 1);
+ });
+ }
+ JS
+ end
+
+ def self.reduce
+ <<-JS
+ function(prev, current) {
+ var count = 0;
+
+ for (index in current) {
+ count += current[index];
+ }
+
+ return count;
+ }
+ JS
+ end
+
+ def self.tag_cloud(opts={})
+ opts.merge({
+ :out => {:inline => true},
+ :raw => true
+ })
+ self.build(opts).find()
+ end
+
+ def self.build
+ Pathways::Session.collection.map_reduce(map, reduce)
+ end
+ end
+end
@@ -0,0 +1,66 @@
+require 'time'
+require 'json'
+
+module Pathways
+ class Parser
+ def initialize(env)
+ path = "#{RAILS_ROOT}/log/#{env || Rails.env}.log"
+ raise ArgumentError unless File.exists?( path )
+ @log = File.open( path )
+ end
+
+ def self.execute(env="development")
+ parser = self.new(env)
+ most_recent_session_updated_at = Pathways::Session.first(:order => "updated_at DESC").try(:updated_at)
+ while true
+ puts "Processing"
+ parser.run(most_recent_session_updated_at)
+ sleep 5;
+ end
+ end
+
+ def run(most_recent_session_updated_at)
+ visits = []
+ @log.each_line do |line|
+
+ next unless timestamp = line.match( /^PathwaysTracker\:(.*)/)
+ visit_hash = JSON.parse(timestamp[1].to_s)
+
+ session = Pathways::Session.find_or_create_by_ip_and_state(visit_hash["ip"], :active)
+
+ updated_at = Time.parse(visit_hash["created_at"]).to_i
+ next if most_recent_session_updated_at and updated_at < most_recent_session_updated_at
+ session.created_at = updated_at if session.created_at.nil?
+ session.updated_at = updated_at
+ most_recent_session_updated_at = updated_at
+
+ last_visit = session.visits.last
+ unless last_visit.nil?
+ next if last_visit.created_at == updated_at
+ time_since_last_visit = updated_at.to_i - last_visit.created_at.to_i
+ if time_since_last_visit > 600
+ session.state = :closed
+ session.save
+ session = Pathways::Session.find_or_create_by_ip_and_state(visit_hash["ip"], :active)
+ session.created_at = updated_at
+ end
+ last_visit.time_active = (time_since_last_visit > 60) ? 60 : time_since_last_visit
+ end
+ puts session.inspect
+ session.updated_at = updated_at
+ visit_opts = {}
+ [:url, :path, :controller, :action].each do | key |
+ visit_opts[key] = visit_hash[key.to_s]
+ end
+ [:created_at].each do | key |
+ visit_opts[key] = Time.parse(visit_hash[key.to_s]).to_i
+ end
+ visit_opts[:session_id] = session.id
+ session.visits << Visit.new(visit_opts)
+ session.time_active = session.updated_at - session.created_at
+ session.save
+ puts session.visits.last.inspect
+ end
+ end
+ end
+end
@@ -0,0 +1,90 @@
+require 'sinatra/base'
+require 'erb'
+require 'pathways'
+
+module Pathways
+ class Server < Sinatra::Base
+ dir = File.dirname(File.expand_path(__FILE__))
+
+ set :views, "#{dir}/server/views"
+ set :public, "#{dir}/server/public"
+ set :static, true
+
+ # Specify a logger to be used by the MongoDB driver
+ # Value can be any that the Logger accepts for initialization
+ # The following is the default setting
+# set :mongo_logfile, File.join("log", "mongo-driver-#{environment}.log")
+
+ helpers do
+ def tab(name)
+ dname = name.to_s.downcase
+ path = (dname == 'home') ? "/" : url_path(dname)
+ "<li #{class_if_current(path)}><a href='#{path}'>#{name}</a></li>"
+ end
+
+ def tabs
+ Pathways::Server.tabs
+ end
+
+ def url_path(*path_parts)
+ [ path_prefix, path_parts ].join("/").squeeze('/')
+ end
+ alias_method :u, :url_path
+
+ def current_section
+ url_path request.path_info.sub('/','').split('/')[0].downcase
+ end
+
+ def current_page
+ url_path request.path_info.sub('/','')
+ end
+
+ def path_prefix
+ request.env['SCRIPT_NAME']
+ end
+
+ def class_if_current(path = '')
+ 'class="current"' if current_page[0, path.size] == path
+ end
+
+ def summary(amount,total)
+ percent = ((amount.to_f / total.to_f)* 100).to_i
+ "#{percent}% of sessions (#{amount} of #{total})"
+ end
+ end
+
+ def show(page, layout = true)
+ erb page.to_sym, {:layout => layout}
+ end
+
+ def self.tabs
+ @tabs ||= ["Home","Sessions", "Actions", "Pages"]
+ end
+
+ # Assuming a MongoMapper document of Post
+ get '/' do
+ show :home
+ end
+
+ get '/sessions' do
+ @sessions = Pathways::Session.all
+ show :sessions
+ end
+
+ get '/actions' do
+ @actions = Pathways::Session.popular_pages(:filter => "controller_action")
+ show :actions
+ end
+
+ get '/pages' do
+ @pages = Pathways::Session.popular_pages(:filter => "controller_action")
+ show :pages
+ end
+
+ get '/sessions/:id' do
+ @session = Pathways::Session.find(params[:id])
+ show :session
+ end
+
+ end
+end
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,95 @@
+// All credit goes to Rick Olson.
+(function($) {
+ $.fn.relatizeDate = function() {
+ return $(this).each(function() {
+ if ($(this).hasClass( 'relatized' )) return
+ $(this).text( $.relatizeDate(this) ).addClass( 'relatized' )
+ })
+ }
+
+ $.relatizeDate = function(element) {
+ return $.relatizeDate.timeAgoInWords( new Date($(element).text()) )
+ }
+
+ // shortcut
+ $r = $.relatizeDate
+
+ $.extend($.relatizeDate, {
+ shortDays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ shortMonths: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
+ months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
+
+ /**
+ * Given a formatted string, replace the necessary items and return.
+ * Example: Time.now().strftime("%B %d, %Y") => February 11, 2008
+ * @param {String} format The formatted string used to format the results
+ */
+ strftime: function(date, format) {
+ var day = date.getDay(), month = date.getMonth();
+ var hours = date.getHours(), minutes = date.getMinutes();
+
+ var pad = function(num) {
+ var string = num.toString(10);
+ return new Array((2 - string.length) + 1).join('0') + string
+ };
+
+ return format.replace(/\%([aAbBcdHImMpSwyY])/g, function(part) {
+ switch(part[1]) {
+ case 'a': return $r.shortDays[day]; break;
+ case 'A': return $r.days[day]; break;
+ case 'b': return $r.shortMonths[month]; break;
+ case 'B': return $r.months[month]; break;
+ case 'c': return date.toString(); break;
+ case 'd': return pad(date.getDate()); break;
+ case 'H': return pad(hours); break;
+ case 'I': return pad((hours + 12) % 12); break;
+ case 'm': return pad(month + 1); break;
+ case 'M': return pad(minutes); break;
+ case 'p': return hours > 12 ? 'PM' : 'AM'; break;
+ case 'S': return pad(date.getSeconds()); break;
+ case 'w': return day; break;
+ case 'y': return pad(date.getFullYear() % 100); break;
+ case 'Y': return date.getFullYear().toString(); break;
+ }
+ })
+ },
+
+ timeAgoInWords: function(targetDate, includeTime) {
+ return $r.distanceOfTimeInWords(targetDate, new Date(), includeTime);
+ },
+
+ /**
+ * Return the distance of time in words between two Date's
+ * Example: '5 days ago', 'about an hour ago'
+ * @param {Date} fromTime The start date to use in the calculation
+ * @param {Date} toTime The end date to use in the calculation
+ * @param {Boolean} Include the time in the output
+ */
+ distanceOfTimeInWords: function(fromTime, toTime, includeTime) {
+ var delta = parseInt((toTime.getTime() - fromTime.getTime()) / 1000);
+ if (delta < 60) {
+ return 'just now';
+ } else if (delta < 120) {
+ return 'about a minute ago';
+ } else if (delta < (45*60)) {
+ return (parseInt(delta / 60)).toString() + ' minutes ago';
+ } else if (delta < (120*60)) {
+ return 'about an hour ago';
+ } else if (delta < (24*60*60)) {
+ return 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago';
+ } else if (delta < (48*60*60)) {
+ return '1 day ago';
+ } else {
+ var days = (parseInt(delta / 86400)).toString();
+ if (days > 5) {
+ var fmt = '%B %d, %Y'
+ if (includeTime) fmt += ' %I:%M %p'
+ return $r.strftime(fromTime, fmt);
+ } else {
+ return days + " days ago"
+ }
+ }
+ }
+ })
+})(jQuery);
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.

0 comments on commit f7d029e

Please sign in to comment.