Skip to content

Instantly share code, notes, and snippets.

@stereosupersonic
Created October 31, 2011 13:56
Show Gist options
  • Save stereosupersonic/1327545 to your computer and use it in GitHub Desktop.
Save stereosupersonic/1327545 to your computer and use it in GitHub Desktop.
analytics_stub
# analytics/app/support/report_invoker.rb
class ReportInvoker
def self.build_reports(dir)
new(dir).build_reports
end
attr_reader :dir
def initialize(dir)
@dir = dir
end
def build_reports
current_generation = start_next_generation
Dir["#{dir}/*.rb"].each do |report_file|
# TODO add logging here
system "bundle exec rails runner 'ReportBuilder.build(\"#{report_file}\", #{current_generation})'"
end
finish_current_generation current_generation
clear_old_generations current_generation-2
end
private
# TODO this method should be put into a global module of some kind
# it is also needed for read out
def read_current_generation
# read the current generation from the analytics_generations table
end
def start_new_generation
current_generation = read_current_generation
# write entry into analytics_generations table for current_generation+1
end
def finish_current_generation(new_generation)
# write the new_generation value to analytics_generations
end
def clear_old_generations(last_generation)
# find all databases with name analytics_data_#{i} where i <= last_generation
# and delete them
end
end
### in Rakefile
task :build_reports do
require "path to report invoker"
ReportInvoker.build_reports File.join(File.dirname(__FILE__), "app", "reports")
end
# analytics/app/support/report_builder.rb
require "base_report"
class ReportBuilder
def self.build(report_file, generation)
new(report_file, generation).build
end
def initialize(report_file, generation)
@report_class = find_report_class
@connection = open_connection generation
end
def build
# pull in the report context,
# ie boot up the Rails environment for the report class
@report_class.load_application
# create the report instance
@report_instance = @report_class.new self
# determine the headers in all the available locales for this report
# these should not depend on the actual data
@report_instance.determine_headers
# now we're ready to rock and roll
@report_instance.calculate
end
def set(table, id, value)
# write value in to table for the row with id
end
def append(table, value)
# append value to the table, generating a new row with a new id
end
private
def find_report_class(report_file)
# load the report ruby file
require report_file
# deduce the report class name from the file name
# list_negotiations_report.rb => ListNegotiationsReport
File.basename(report_file).classify.constantize
end
def open_connection(generation)
# open mongo_db connection to table analytics_data_#{generation}
end
end
# analytics/app/support/report_reader.rb
class ReportReader
end
# analytics/app/support/base_report.rb
class BaseReport
def self.define_context(app)
class << self
define_method :context do
app
end
define_method :load_application do
analytics_root = File.join(File.dirname(__FILE__), "..", "..")
if File.exist? File.join(analytics_root, "app/support/#{app}_support.rb")
require File.join(analytics_root, "app/support/#{app}_support")
if Object.const_defined? "#{app}_support".classify
include "#{app}_support".classify.constantize
end
end
require File.join(analytics_root, "..", app.to_s, "config", "environment")
end
end
end
def self.report_name
self.to_s.sub("Report", "").underscore
end
attr_reader :builder
def initialize(builder)
@builder = builder
end
def write(line, specific_id=nil)
@builder.append build_name(:values, self.class.report_name), line.generic
@builder.append build_name(:values, self.class.report_name, specific_id), line.all if specific_id
end
def set_headers(headers, locale, specific_id=nil)
mapping = {}
order = []
headers.each do |header|
key = headers.keys.first
mapping[key] = header[key]
order << key
end
@builder.set build_name(:row_order, self.class.report_name), # table_name
build_name(:order, specific_id), # row_id
order # value
# set, will set the value of row_id in table_name to content
@builder.set build_name(:headers, self.class.report_name), # table_name
build_name(locale, specific_id), # row_id
headers # content of row
end
private
def build_name(*parts)
parts.compact.map(&:to_s).join("_")
end
end
# These support module should go into a seperate directory "analytics/app/support"
module KatalysatorSupport
def add_shop_field_headers(shop, headers)
shop.shop_fields.reject(&:hidden?).each do |shop_field|
headers << { "shop_field_#{shop_field.id}".to_sym => shop_field.name }
end
end
def add_shop_fields(project, result)
project.shop.shop_fields.reject(&:hidden?).each do |shop_field|
project_field = project.project_fields.detect { |pf| pf.shop_field_id == shop_field.id }
result << { "shop_field_#{shop_field.id}".to_sym => project_field.value } if project_field
end
end
def shop_categories(shop)
Service.statistics_categories_for_shop(shop)
end
end
# example for one report class
# analytics/app/reports/booked_offer_acceptance_report.rb
class BookedOfferAcceptancesReport < BaseReport
GENERIC_HEADERS = [:service_provider_name, :service_provider_id, :service_provider_city, :service_provider_country, :service_provider_chain, :service_provider_pp,
:project_number, :negotiation_status, :project_title, :project_start_date, :project_end_date, :offer_acceptance_date, :project_create_date, :project_creator,
:project_creator_group, :project_creator_company_name, :project_invoice_address_name, :project_manager, :project_manager_group, :offer_total_currency,
:offer_total_with_tax, :offer_savings, :offer_lost_savings, :offer_won_savings, :tender_splitting, :commission_rate_conference, :commission_rate_lodging]
define_context :katalysator
def determine_headers
# TODO find all defined locales, see Rails i18n
LOCALES.each do |locale|
generic = []
# TODO set the right locale
GENERIC_HEADERS.each { |h| generic << { h => I18n.t(h, :prefix => "analytics.headers") } }
set_header generic, locale
Shop.all.each do |shop|
specific = generic.dup
add_shop_field_headers(shop, specific)
# TODO see if this worcs out for the statitics categories
shop_categories(shop).each do |category|
cat_name = category.underscore
specific << { "amount_#{category}" => I18n.t("amount_#{cat_name}", :prefix => "analytics.statistics_categories") }
specific << { "total_#{category}" => I18n.t("total_#{cat_name}", :prefix => "analytics.statistics_categories") }
set_header specific, locale, "shop_#{shop.id}"
end
end
end
end
def calculate
get_authorized_offers do |offer|
# the method should update the exception counter for offer
# and log the exception under "offer_#{id}"
exception_logged "offer", offer.id do
shop = offer.shop
write build_line(offer), "shop_#{shop.id}"
end
end
end
private
def build_line(offer)
service_provider = find_service_provider(o.negotiation.service_provider_id,o.shop)
build_offer(offer, service_provider).tap do |result|
result.specific do |line|
# add shop specific fields
add_shop_fields offer.project, line
# add shop specific service category aggregations
shop_categories(offer.shop).each do |category|
if category == "Sonstiges"
# all services which are not categorized will be summarized in Sonstiges
line["amount_#{category}"] = format_number(offer.calculator.amount_for_statistics_category(cat)+offer.calculator.amount_for_statistics_category(""))
line["total_#{category}"] = display_price(offer.calculator.with_tax_total_for_statistics_category(cat)+offer.calculator.with_tax_total_for_statistics_category(""))
else
line["amount_#{category}"] = format_number(offer.calculator.amount_for_statistics_category(cat))
line["total_#{category}"] = display_price(offer.calculator.with_tax_total_for_statistics_category(cat))
end
end
end
end
end
def build_offer(offer, service_provider)
ReportLine.new.tap do |result|
result.generic do |l|
# TODO move i18n of specific answers to readout phase
l[:service_provider_name] = (service_provider.name rescue I18n.t('common.unknown')
l[:service_provider_id] = (o.negotiation.service_provider_id rescue I18n.t('common.unknown'))
l[:service_provider_city] = (service_provider.city.to_s rescue I18n.t('common.unknown'))
l[:service_provider_country] = (service_provider.land.to_s rescue I18n.t('common.unknown'))
l[:service_provider_chain] = (service_provider.kette.blank? ? I18n.t('common.unknown') : o.negotiation.service_provider.kette rescue I18n.t('common.unknown'))
l[:service_provider_pp] = (service_provider.prio ? I18n.t('common.positive_answer') : I18n.t('common.negative_answer') rescue "")
l[:project_number] = o.project.project_number.to_s
l[:negotiation_status] = ProjectsHelper.negotiation_status(o.negotiation)
l[:project_title] = o.project.title
l[:project_start_date] = format_date(o.project.start_date.to_date)
l[:project_end_date] = format_date(o.project.end_date.to_date)
l[:offer_acceptance_date] = (format_date(o.offer_acceptance.created_at.to_date) rescue "")
l[:project_create_date] = format_date(o.project.created_at.to_date)
l[:project_creator] = (o.project.creator_full_name rescue I18n.t('common.unknown'))
l[:project_creator_group] = o.project.user_group_name(:creator).to_s
l[:project_creator_company_name] = (o.project.creator_company_name rescue I18n.t('common.unknown'))
l[:project_invoice_address_name] = o.project.invoice_address_name
l[:project_manager] = (o.project.manager_full_name rescue I18n.t('common.unknown'))
l[:project_manager_group] = o.project.user_group_name(:manager).to_s
l[:offer_total_currency] = currency_iso(o.total_with_tax)
l[:offer_total_with_tax] = display_price(o.total_with_tax)
l[:offer_savings] = (display_price(o.negotiation.has_positive_savings? ? o.negotiation.savings : 0) rescue 'error')
l[:offer_lost_savings] = (display_price(o.lost_saving) rescue 'error')
l[:offer_won_savings] = (display_price(o.won_saving) rescue 'error')
l[:tender_splitting] = o.tender.splitting ? I18n.t('common.positive_answer') : I18n.t('common.negative_answer')
l[:commission_rate_conference] = display_percent(o.offer_info.commission_rate_conference)
l[:commission_rate_lodging] = display_percent(o.offer_info.commission_rate_lodging
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment