class Jekyll::GoogleAnalytics

Public Instance Methods

filter_url(url) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 276
def filter_url(url)
    if url.include? ".html"
      url = url.sub(".html", "")
    end
      
    if url.include? "index"
      url = url.sub("index", "")
    end
    
    return url
end
float?(string) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 288
def float?(string)
  true if Float(string) rescue false
end
generate(site) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 19
def generate(site)    
  unless site.config['jekyll_ga']
    return
  end
    
  Jekyll.logger.info "Jekyll GA:","Initializating"
  startTime = Time.now

  # Set "ga" to store the current config
  ga = site.config['jekyll_ga']
    
  # Local cache setup so we don't hit the sever X amount of times for same data.
  cache_directory = ga['cache_directory'] || "_jekyll_ga"
  cache_filename = ga['cache_filename'] || "ga_cache.json"
  cache_file_path = cache_directory + "/" + cache_filename

  # Set the refresh rate in minutes (how long the program will wait in minutes before writing a new file)
  refresh_rate = ga['refresh_rate'] || 60

  # If the directory doesn't exist lets make it
  if not Dir.exist?(cache_directory)
    Dir.mkdir(cache_directory)
  end

  # Now lets check for the cache file and how old it is (if it exceeds refesh_rate then update it)
  if File.exist?(cache_file_path) and ((Time.now - File.mtime(cache_file_path)) / 60 < refresh_rate) and !ga["debug"]
    # Inject from cache
    data = JSON.parse(File.read(cache_file_path))  
      
    # Into pages...
    site.pages.each { |page|
        page.data["stats"] = data["page-stats"][get_identifier_for("page", page)]
    }  
      
    # Into posts...
    site.posts.docs.each { |post|
        post.data["stats"] = data["post-stats"][get_identifier_for("post", post)]
    } 
      
    # Into site...
    site.data["stats"] = data["site-stats"] 
    site.data["period"] = data["period"]
    site.data["headers"] = data["headers"]
  else
    analytics = Google::Apis::AnalyticsV3::AnalyticsService.new
      
     # Load our credentials for the service account (using env vars)
    auth = ::Google::Auth::ServiceAccountCredentials
        .make_creds(scope: 'https://www.googleapis.com/auth/analytics')
      
    # Assign auth
    analytics.authorization = auth
      
    # Get pages && posts from site (filtering its urls)
    pages = site.pages.select { |page| page.name.include? ".html" }.collect { |page| filter_url(page.dir + page.name) }
    posts = site.posts.docs.collect { |doc| filter_url(doc.url.to_s) }
      
    # Concat the two arrays
    pages.push(*posts)
      
    # Create a queryString (string type) from the array
    queryString = pages.collect { |page| "ga:pagePath==#{page.to_s}" }.join(",")
    
    # Get the response
    response = get_response(analytics, ga, queryString)
      
    # Declare the hash where the info will go
    store_data = {}
    
    # Make another request to Google Analytics API to get the pasterence
    if ga["compare_period"]
       start_date = Chronic.parse(ga['start']).strftime("%Y-%m-%d")
       end_date = Chronic.parse(ga['end']).strftime("%Y-%m-%d")
        
       diff_date = end_date.to_date - start_date.to_date
       diff_date = diff_date.numerator.to_i - 1
        
       site.data["period"] = diff_date
       store_data.store("period", diff_date)
        
       @past_response = get_response(analytics, ga, queryString, start_date.to_date - diff_date, start_date) 
    end

    # If there are errors then show them
    if response.kind_of?(Array) and response.include? "error"
        errors = reponse["error"]["errors"]
        
        errors.each { |error|
            Jekyll.logger.error "Jekyll GoogleAnalytics:", "Client Execute Error: #{error.message}"   
        }
        
        raise RuntimeError, "Check errors from Google Analytics"
    end

    @response_data = response
      
    # Get keys from columnHeaders
    @headers = @response_data.column_headers.collect { |header| header.name.sub("ga:", "") }

    # Loop through pages && posts to add the stats object value
    page_data = {}
    site.pages.each { |page|
        stats_data = get_stats_for(ga, "page", page)
        page.data["stats"] = stats_data
        
        # Jekyll.logger.info "GA-debug (stats-type): ", page.data["statistics"].class.to_s

        unless stats_data.nil?
          page_data.store(get_identifier_for("page", page), stats_data)
            
          if ga["debug"]
            Jekyll.logger.info "GA-debug (page-stats): ", page.data["stats"].to_json
          end
        end
    }
    store_data.store("page-stats", page_data)

    post_data = {}
    site.posts.docs.each { |post|
        stats_data = get_stats_for(ga, "post", post)
        post.data["stats"] = stats_data

        unless stats_data.nil?
          post_data.store(get_identifier_for("post", post), stats_data)
            
          if ga["debug"]
            Jekyll.logger.info "GA-debug (post-stats): ", post.data["stats"].to_json
          end
        end
    }
    store_data.store("post-stats", post_data)

    # Do the same for the site
    stats_data = get_stats_for(ga, "site")
    site.data["stats"] = stats_data
      
    store_data.store("site-stats", stats_data)

    if !stats_data.nil? and ga["debug"]
      Jekyll.logger.info "GA-debug (site-stats): ", site.data["stats"].to_json
    end
    
    # Before saving modify headers
    
    # Create a new array with the value, the diff and the perc from the last stored stats_data corresponding to the site one
    new_headers = []
    @headers.each { |header|
        unless stats_data[header].nil?            
            protoheader = {}

            protoheader.store("name", header)
            protoheader.store("value", stats_data[header])
            protoheader.store("diff_value", stats_data["diff_#{header}"])
            protoheader.store("value_perc", stats_data["#{header}_perc"])

            new_headers.push(protoheader)
        end
    }
      
    # Then save...
    site.data["headers"] = new_headers
    store_data.store("headers", new_headers)

    # Write the response data
    if File.exist?(cache_file_path) and ((Time.now - File.mtime(cache_file_path)) / 60 >= refresh_rate) and ga["debug"] or !ga["debug"] or !File.exist?(cache_file_path)
        File.open(cache_file_path, "w") do |f|
          f.write(JSON.pretty_generate(store_data))
        end
    end
  end
    
  endTime = Time.now - startTime

  Jekyll.logger.info "Jekyll GoogleAnalytics:", "Initializated in #{endTime} seconds"
end
get_identifier_for(page_type, inst) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 195
def get_identifier_for(page_type, inst)
    if page_type == "page"
        return filter_url(inst.dir + inst.name)
    elsif page_type == "post"
        return filter_url(inst.url.to_s)
    end
end
get_response(analytics, ga, queryString, tstart = nil, tend = nil) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 261
def get_response(analytics, ga, queryString, tstart = nil, tend = nil)
   return analytics.get_ga_data(
            ga['profileID'], # ids
            tstart.nil? ? Chronic.parse(ga['start']).strftime("%Y-%m-%d") : tstart.to_s, # start_date
            tend.nil? ? Chronic.parse(ga['end']).strftime("%Y-%m-%d") : tend.to_s,   # end_date
            ga['metrics'],  # metrics
            dimensions: ga['dimensions'],
            filters: ga["filters"].to_s.empty? ? queryString : ga["filters"].to_s,
            include_empty_rows: nil,
            max_results: ga["max_results"].nil? ? 10000 : ga["max_results"].to_i, 
            output: nil, 
            sampling_level: nil, 
            segment: ga['segment']) 
end
get_site_data(is_past) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 254
def get_site_data(is_past)
    data = (is_past ? @past_response : @response_data).totals_for_all_results
    data.keys.each { |k| data[k.sub("ga:", "")] = data[k]; data.delete(k) }
    
    return data;
end
get_stats_for(ga, page_type, inst = nil) click to toggle source
# File lib/jekyll-ga-v2/jekyll-ga-v2.rb, line 203
def get_stats_for(ga, page_type, inst = nil)
   data = nil
   past_data = nil
    
   # Transpose array into hash using columnHeaders
   if page_type == "page"
      data = @response_data.rows.select { |row| row[0] == filter_url(inst.dir + inst.name) }.collect { |row| Hash[ [@headers, row].transpose ] }[0]
      past_data = @past_response.nil? or !@past_response.nil? and @past_response.rows.nil? ? nil : @past_response.rows.select { |row| row[0] == filter_url(inst.dir + inst.name) }.collect { |row| Hash[ [@headers, row].transpose ] }[0]
   elsif page_type == "post"
      data = @response_data.rows.select { |row| row[0] == filter_url(inst.url.to_s) }.collect { |row| Hash[ [@headers, row].transpose ] }[0]
      past_data = @past_response.nil? or !@past_response.nil? and @past_response.rows.nil? ? nil : @past_response.rows.select { |row| row[0] == filter_url(inst.url.to_s) }.collect { |row| Hash[ [@headers, row].transpose ] }[0]
   elsif page_type == "site"
      data = get_site_data(false)
      past_data = get_site_data(true)
   end
    
   if data.nil?
       return nil
   end
    
   # Create diff_xxx and xxx_perc keys for data
   if ga["compare_period"]
       pre_data = {}
       
       data.each { |key, value|
            present_value = value.to_f

            past_value = nil

            if past_data.kind_of?(Hash)
               past_value = past_data.fetch(key, 0.0).to_f
            else
               past_value = 0.0 
            end

            if float?(value) and float?(past_value) # Filter for pagePath (not float or integer value)
                # Thanks to: https://stackoverflow.com/q/31981133/3286975
                diff_value = present_value - past_value
                perc_value = present_value / past_value * 100.0

                pre_data.store("diff_#{key}", diff_value)
                pre_data.store("#{key}_perc", perc_value == Float::INFINITY ? "∞" : (perc_value.nan? ? "0" : perc_value.to_s))
            end
       }

       data.merge!(pre_data)
   end
    
   return data
end