1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
# frozen_string_literal: true
module Grafana
# Allows for easy formatting and manipulations of timestamps
# coming from a Grafana url
class TimeWindow
include ::Gitlab::Utils::StrongMemoize
def initialize(from, to)
@from = from
@to = to
end
def formatted
{
start: window[:from].formatted,
end: window[:to].formatted
}
end
def in_milliseconds
window.transform_values(&:to_ms)
end
private
def window
strong_memoize(:window) do
specified_window
rescue Timestamp::Error
default_window
end
end
def specified_window
RangeWithDefaults.new(
from: Timestamp.from_ms_since_epoch(@from),
to: Timestamp.from_ms_since_epoch(@to)
).to_hash
end
def default_window
RangeWithDefaults.new.to_hash
end
end
# For incomplete time ranges, adds default parameters to
# achieve a complete range. If both full range is provided,
# range will be returned.
class RangeWithDefaults
DEFAULT_RANGE = 8.hours
# @param from [Grafana::Timestamp, nil] Start of the expected range
# @param to [Grafana::Timestamp, nil] End of the expected range
def initialize(from: nil, to: nil)
@from = from
@to = to
apply_defaults!
end
def to_hash
{ from: @from, to: @to }.compact
end
private
def apply_defaults!
@to ||= @from ? relative_end : Timestamp.new(Time.now)
@from ||= relative_start
end
def relative_start
Timestamp.new(DEFAULT_RANGE.before(@to.time))
end
def relative_end
Timestamp.new(DEFAULT_RANGE.since(@from.time))
end
end
# Offers a consistent API for timestamps originating from
# Grafana or other sources, allowing for formatting of timestamps
# as consumed by Grafana-related utilities
class Timestamp
Error = Class.new(StandardError)
attr_accessor :time
# @param timestamp [Time]
def initialize(time)
@time = time
end
# Formats a timestamp from Grafana for compatibility with
# parsing in JS via `new Date(timestamp)`
def formatted
time.utc.strftime('%FT%TZ')
end
# Converts to milliseconds since epoch
def to_ms
time.to_i * 1000
end
class << self
# @param time [String] Representing milliseconds since epoch.
# This is what JS "decided" unix is.
def from_ms_since_epoch(time)
return if time.nil?
raise Error, 'Expected milliseconds since epoch' unless ms_since_epoch?(time)
new(cast_ms_to_time(time))
end
private
def cast_ms_to_time(time)
Time.at(time.to_i / 1000.0)
end
def ms_since_epoch?(time)
ms = time.to_i
ms.to_s == time && ms.bit_length < 64
end
end
end
end
|