# 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