gitlab-org--gitlab-foss/lib/gitlab/analytics/date_filler.rb

113 lines
3.1 KiB
Ruby

# frozen_string_literal: true
module Gitlab
module Analytics
# This class generates a date => value hash without gaps in the data points.
#
# Simple usage:
#
# > # We have the following data for the last 5 day:
# > input = { 3.days.ago.to_date => 10, Date.today => 5 }
#
# > # Format this data, so we can chart the complete date range:
# > Gitlab::Analytics::DateFiller.new(input, from: 4.days.ago, to: Date.today, default_value: 0).fill
# > {
# > Sun, 28 Aug 2022=>0,
# > Mon, 29 Aug 2022=>10,
# > Tue, 30 Aug 2022=>0,
# > Wed, 31 Aug 2022=>0,
# > Thu, 01 Sep 2022=>5
# > }
#
# Parameters:
#
# **input**
# A Hash containing data for the series or the chart. The key is a Date object
# or an object which can be converted to Date.
#
# **from**
# Start date of the range
#
# **to**
# End date of the range
#
# **period**
# Specifies the period in wich the dates should be generated. Options:
#
# - :day, generate date-value pair for each day in the given period
# - :week, generate date-value pair for each week (beginning of the week date)
# - :month, generate date-value pair for each week (beginning of the month date)
#
# Note: the Date objects in the `input` should follow the same pattern (beginning of ...)
#
# **default_value**
#
# Which value use when the `input` Hash does not contain data for the given day.
#
# **date_formatter**
#
# How to format the dates in the resulting hash.
class DateFiller
DEFAULT_DATE_FORMATTER = -> (date) { date }
PERIOD_STEPS = {
day: 1.day,
week: 1.week,
month: 1.month
}.freeze
def initialize(
input,
from:,
to:,
period: :day,
default_value: nil,
date_formatter: DEFAULT_DATE_FORMATTER)
@input = input.transform_keys(&:to_date)
@from = from.to_date
@to = to.to_date
@period = period
@default_value = default_value
@date_formatter = date_formatter
end
def fill
data = {}
current_date = from
loop do
transformed_date = transform_date(current_date)
break if transformed_date > to
formatted_date = date_formatter.call(transformed_date)
value = input.delete(transformed_date)
data[formatted_date] = value.nil? ? default_value : value
current_date = (current_date + PERIOD_STEPS.fetch(period)).to_date
end
raise "Input contains values which doesn't fall under the given period!" if input.any?
data
end
private
attr_reader :input, :from, :to, :period, :default_value, :date_formatter
def transform_date(date)
case period
when :day
date.beginning_of_day.to_date
when :week
date.beginning_of_week.to_date
when :month
date.beginning_of_month.to_date
else
raise "Unknown period given: #{period}"
end
end
end
end
end