3scale/porta

View on GitHub
app/lib/stats/views/usage.rb

Summary

Maintainability
B
4 hrs
Test Coverage
 
module Stats
module Views
module Usage
extend ActiveSupport::Concern
 
included do
include System::UrlHelpers.system_url_helpers
end
 
GRANULARITIES = {:year => :month,
:month => :day,
:week => 6.hours,
:day => :hour}.with_indifferent_access
 
ALLOWED_GRANULARITIES = GRANULARITIES.values
 
ALLOWED_TIME_RANGES = {:month => 10.years,
:day => 1.year,
6.hours => 6.months, # 6.hours looks unused, but since it's a valid granularity we need to check it as well
:hour => 90.days}.with_indifferent_access
 
Method `usage` has 43 lines of code (exceeds 25 allowed). Consider refactoring.
Stats::Views::Usage#usage has approx 15 statements
def usage(options)
range, granularity, metric = extract_range_and_granularity_and_metric(options)
 
data = usage_values_in_range(range, granularity, metric) # metric can be a response_code
 
total = data.sum
 
result = {
metric.class.name.underscore.to_sym => detail(metric),
:period => period_detail(options),
:total => total,
:values => data
}
 
Stats::Views::Usage#usage performs a nil-check
unless @cinstance.nil?
application_id = @cinstance.id
Stats::Views::Usage#usage calls '@cinstance.user_account' 2 times
account_id = @cinstance.user_account.id
 
result[:application] = {
:id => application_id,
:name => @cinstance.name,
:state => @cinstance.state,
:link => provider_admin_application_path(application_id),
:description => @cinstance.description,
:plan => {
Stats::Views::Usage#usage calls '@cinstance.plan' 2 times
:id => @cinstance.plan.id,
:name => @cinstance.plan.name
},
:account => {
:id => account_id,
:name => @cinstance.user_account.org_name,
:link => admin_buyers_account_path(account_id)
},
service: {
id: @cinstance.service_id
}
}
end
 
return result if options.fetch(:skip_change, true)
 
if granularity.to_s == 'day'
Stats::Views::Usage#usage calls 'range.previous' 3 times
previous_range = range.class.new(range.previous.begin.midnight, range.previous.end.midnight) # this is to keep us from breaking in DST.......
previous_data = usage_values_in_range(previous_range, granularity, metric)
else
previous_data = usage_values_in_range(range.previous, granularity, metric)
end
previous_total = previous_data.sum
result[:previous_total] = previous_total
result[:change] = total.percentage_change_from(previous_total)
 
result
end
 
Stats::Views::Usage#usage_progress has approx 10 statements
def usage_progress(options)
range, granularity, metric = extract_range_and_granularity_and_metric(options)
 
current_data = usage_values_in_range(range, granularity, metric) # can be Metric or ResponseCode
Stats::Views::Usage#usage_progress calls 'range.previous' 2 times
previous_range = range.class.new(range.previous.begin.midnight, range.previous.end.midnight - 1) # this is to keep us from breaking in DST.......
Stats::Views::Usage#usage_progress calls 'options[:skip_change]' 2 times
previous_data = usage_values_in_range(previous_range, granularity, metric) unless options[:skip_change]
# previous_data = usage_values_in_range(range.previous, granularity, metric) unless options[:skip_change]
total = current_data.sum
previous_total = previous_data.sum
 
rslt = {
data: {
total: total,
values: current_data,
previous_total: previous_total
}
}
rslt.merge!(detail(metric))
rslt.deep_merge!(data: { change: total.percentage_change_from(previous_total) }) unless options[:skip_change]
rslt
end
 
def usage_progress_for_buyer_methods(options)
#source.first => service, source.last => app
methods = source.first.method_metrics.select do |method|
Stats::Views::Usage#usage_progress_for_buyer_methods calls 'source.last.plan' 2 times
Stats::Views::Usage#usage_progress_for_buyer_methods calls 'source.last' 2 times
method.enabled_for_plan?(source.last.plan) &&
method.visible_in_plan?(source.last.plan)
end
 
usage_for_all(methods, options)
end
 
def usage_progress_for_all_methods(options)
usage_for_all( source.first.method_metrics, options)
end
 
def usage_progress_for_all_metrics(options)
usage_for_all( source.first.metrics.top_level, options)
end
 
private
 
def usage_for_all(items, options)
metrics = items.inject([]) do |memo, item|
memo << usage_progress(options.merge(:metric => item))
end
{
:period => period_detail(options),
:metrics => metrics
}
end
 
def usage_values_in_range(range, granularity, metric)
storage.values_in_range(range, granularity, [:stats, source_key, metric])
end
 
def extract_range_and_granularity_and_metric(options)
options = options.to_h.symbolize_keys
 
range, granularity = extract_range_and_granularity(options)
validate_time_range(range, granularity)
metric = extract_metric(options)
 
[range, granularity, metric]
end
 
Method `extract_range_and_granularity` has a Cognitive Complexity of 20 (exceeds 5 allowed). Consider refactoring.
Stats::Views::Usage#extract_range_and_granularity has approx 16 statements
def extract_range_and_granularity(options)
Stats::Views::Usage#extract_range_and_granularity refers to 'options' more than self (maybe move it to another class?)
Stats::Views::Usage#extract_range_and_granularity calls 'options[:period]' 2 times
if options[:period]
period = sanitize_period(options[:period])
Stats::Views::Usage#extract_range_and_granularity calls 'options[:granularity]' 6 times
granularity = options[:granularity] || GRANULARITIES[period]
length = 1.send(period)
 
Stats::Views::Usage#extract_range_and_granularity calls 'extract_timezone(options)' 2 times
timezone = extract_timezone(options)
Stats::Views::Usage#extract_range_and_granularity calls 'options[:since]' 3 times
range_since = to_time(options[:since].presence || timezone.now - length, timezone)
range_until = (range_since + length - 1.second).end_of_minute # taking a second away means excluding the extra day in case of a month, etc
 
sanitize_range_and_granularity(range_since..range_until, granularity)
else
raise InvalidParameterError, "Missing parameter :granularity" unless options.key?(:granularity)
# due to the unfortunate use of 21600 as a valid granularity the parameter is required to a symbol or fixnum
raise InvalidParameterError, "Granularity must be one of #{ALLOWED_GRANULARITIES.inspect}, not #{options[:granularity]}" unless ALLOWED_GRANULARITIES.include?(options[:granularity]) || ALLOWED_GRANULARITIES.include?(options[:granularity].to_sym)
 
Stats::Views::Usage#extract_range_and_granularity calls 'options[:until]' 2 times
if options[:since].present? && options[:until].present?
timezone = extract_timezone(options)
range = to_time(options[:since], timezone)..to_time(options[:until], timezone)
sanitize_range_and_granularity(range, options[:granularity])
Stats::Views::Usage#extract_range_and_granularity calls 'options[:range]' 2 times
elsif options[:range].present?
sanitize_range_and_granularity(options[:range], options[:granularity])
else
raise InvalidParameterError, "You need to specify either 'range' or 'since' and 'until'"
end
 
end
Stats::Views::Usage#extract_range_and_granularity has the variable name 'e'
rescue ThreeScale::HashHacks::MissingKeyError => e
raise InvalidParameterError, e.to_s
end
 
protected
 
def sanitize_period(period)
if GRANULARITIES.has_key?(period)
return period
else
raise InvalidParameterError, "Period must be one of #{GRANULARITIES.keys.inspect} not #{period.inspect}"
end
end
 
def sanitize_range_and_granularity(range, granularity)
granularity = Stats::Aggregation.normalize_granularity(granularity)
range = range.to_time_range.round(granularity)
 
[range, granularity]
end
 
def validate_time_range(range, granularity)
allowed_time = ALLOWED_TIME_RANGES[granularity]
return unless allowed_time
 
raise InvalidParameterError, "Time range for the granularity must be less than #{allowed_time.inspect}" if range.length > allowed_time
end
end
end
end