blob: 6cc757d77c553a92d20b46861000d32ae730b8d9 (
plain)
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
|