Class: GoodJob::PerformanceRange

Inherits:
Object
  • Object
show all
Defined in:
app/charts/good_job/performance_range.rb

Constant Summary collapse

PARAMETER_KEYS =
%w[chart_range chart_start chart_end].freeze
TIME_ZONE_PARAMETER_KEY =
"chart_time_zone"
INPUT_PARAMETER_KEYS =
[*PARAMETER_KEYS, TIME_ZONE_PARAMETER_KEY].freeze
DEFAULT_KEY =
"24h"
MAXIMUM_TIME_SERIES_COORDINATES =
30
MAXIMUM_TIME_ZONE_LENGTH =
255
MINIMUM_YEAR =

Restrict custom bounds to portable four-digit years in the timezone used by the page.

1000
MAXIMUM_YEAR =
9999
MINIMUM_LOCAL_VALUE =
"1000-01-01T00:00:00"
MAXIMUM_LOCAL_VALUE =
"9999-12-31T23:59:59"
TIMESTAMP_PATTERN =
/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\z/
LOCAL_TIMESTAMP_PATTERN =
/\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?\z/
OPTIONS =
{
  "1h" => {
    label: "1h",
    duration: 1.hour,
    interval_seconds: 5.minutes.to_i,
    label_format: "%H:%M",
    label_style: "time",
  },
  "6h" => {
    label: "6h",
    duration: 6.hours,
    interval_seconds: 15.minutes.to_i,
    label_format: "%H:%M",
    label_style: "time",
  },
  "24h" => {
    label: "24h",
    duration: 24.hours,
    interval_seconds: 1.hour.to_i,
    label_format: "%H:%M",
    label_style: "time",
  },
  "7d" => {
    label: "7d",
    duration: 24.hours * 7,
    interval_seconds: 6.hours.to_i,
    label_format: "%b %-d %H:%M",
    label_style: "date_time",
  },
}.freeze
SEMANTIC_INTERVALS =

All candidates become integer seconds. The 30-day and 365-day scales are fixed elapsed durations for readable long-range charts, not calendar month or year aggregation.

[
  *[1, 2, 5, 10, 15, 30].map(&:seconds),
  *[1, 2, 5, 10, 15, 30].map(&:minutes),
  *[1, 2, 3, 6, 12].map(&:hours),
  *[1, 2, 3, 5, 7, 14].map(&:days),
  *[1, 2, 3, 6].map { |multiple| multiple * 30.days },
  *[1, 2, 5, 10, 20, 50, 100, 200, 500].map { |multiple| multiple * 365.days },
].map(&:to_i).uniq.sort.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = nil, query_string: nil, **parameter_keywords) ⇒ PerformanceRange

Returns a new instance of PerformanceRange.



63
64
65
66
67
68
# File 'app/charts/good_job/performance_range.rb', line 63

def initialize(params = nil, query_string: nil, **parameter_keywords)
  @params = params || parameter_keywords
  @repeated_parameter_keys = repeated_parameter_keys(query_string)
  @local_time_zone = local_time_zone
  resolve
end

Instance Attribute Details

#end_time ⇒ Object (readonly)

Returns the value of attribute end_time.



61
62
63
# File 'app/charts/good_job/performance_range.rb', line 61

def end_time
  @end_time
end

#interval_seconds ⇒ Object (readonly)

Returns the value of attribute interval_seconds.



61
62
63
# File 'app/charts/good_job/performance_range.rb', line 61

def interval_seconds
  @interval_seconds
end

#key ⇒ Object (readonly)

Returns the value of attribute key.



61
62
63
# File 'app/charts/good_job/performance_range.rb', line 61

def key
  @key
end

#label_format ⇒ Object (readonly)

Returns the value of attribute label_format.



61
62
63
# File 'app/charts/good_job/performance_range.rb', line 61

def label_format
  @label_format
end

#label_style ⇒ Object (readonly)

Returns the value of attribute label_style.



61
62
63
# File 'app/charts/good_job/performance_range.rb', line 61

def label_style
  @label_style
end

#start_time ⇒ Object (readonly)

Returns the value of attribute start_time.



61
62
63
# File 'app/charts/good_job/performance_range.rb', line 61

def start_time
  @start_time
end

Instance Method Details

#apply(relation) ⇒ Object



70
71
72
# File 'app/charts/good_job/performance_range.rb', line 70

def apply(relation)
  relation.where(scheduled_at: start_time...end_time)
end

#canonical_parameters?(query_parameters) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
# File 'app/charts/good_job/performance_range.rb', line 74

def canonical_parameters?(query_parameters)
  query_parameters.slice(*INPUT_PARAMETER_KEYS) == to_params
end

#canonical_timestamp(timestamp) ⇒ Object



99
100
101
102
103
# File 'app/charts/good_job/performance_range.rb', line 99

def canonical_timestamp(timestamp)
  timestamp = timestamp.in_time_zone
  # ISO 8601 cannot preserve the sub-minute historical offsets present in some IANA zones.
  timestamp.utc_offset.remainder(60).zero? ? timestamp.iso8601 : timestamp.utc.iso8601
end

#chart_timestamp_label(timestamp) ⇒ Object



78
79
80
# File 'app/charts/good_job/performance_range.rb', line 78

def chart_timestamp_label(timestamp)
  timestamp.in_time_zone.strftime(label_format)
end

#chart_timestamp_labels(timestamps) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'app/charts/good_job/performance_range.rb', line 82

def chart_timestamp_labels(timestamps)
  labels = timestamps.map { |timestamp| chart_timestamp_label(timestamp) }

  labels.each_index.group_by { |index| labels[index] }.each_value do |indices|
    next unless indices.many?

    offsets = indices.map { |index| timestamps[index].in_time_zone.formatted_offset }
    next unless offsets.uniq.many?

    indices.zip(offsets).each do |index, offset|
      labels[index] = "#{labels[index]} #{offset}"
    end
  end

  labels
end

#custom? ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
# File 'app/charts/good_job/performance_range.rb', line 105

def custom?
  key.nil?
end

#custom_params ⇒ Object



109
110
111
112
113
114
# File 'app/charts/good_job/performance_range.rb', line 109

def custom_params
  {
    "chart_start" => canonical_timestamp(start_time),
    "chart_end" => canonical_timestamp(end_time),
  }
end

#default? ⇒ Boolean

Returns:

  • (Boolean)


116
117
118
# File 'app/charts/good_job/performance_range.rb', line 116

def default?
  to_params.empty?
end

#end_label ⇒ Object



120
121
122
# File 'app/charts/good_job/performance_range.rb', line 120

def end_label
  range_label(end_time)
end

#end_local_value ⇒ Object



124
125
126
# File 'app/charts/good_job/performance_range.rb', line 124

def end_local_value
  local_value(end_time)
end


128
129
130
131
132
133
134
135
136
137
# File 'app/charts/good_job/performance_range.rb', line 128

def navigation_params
  return to_params unless key

  # A preset is a relative definition; navigation also carries this evaluated instance.
  {
    "chart_range" => key,
    "chart_start" => canonical_timestamp(start_time),
    "chart_end" => canonical_timestamp(end_time),
  }
end

#options ⇒ Object



139
140
141
# File 'app/charts/good_job/performance_range.rb', line 139

def options
  OPTIONS.map { |option_key, option| { key: option_key, label: option.fetch(:label) } }
end

#reload_params ⇒ Object



143
144
145
146
147
# File 'app/charts/good_job/performance_range.rb', line 143

def reload_params
  return to_params if custom? || default?

  { "chart_range" => key }
end

#start_end_binds ⇒ Object



149
150
151
152
153
154
# File 'app/charts/good_job/performance_range.rb', line 149

def start_end_binds
  [
    query_attribute("start_time", start_time, ActiveRecord::Type::DateTime.new),
    query_attribute("end_time", end_time, ActiveRecord::Type::DateTime.new),
  ]
end

#start_label ⇒ Object



156
157
158
# File 'app/charts/good_job/performance_range.rb', line 156

def start_label
  range_label(start_time)
end

#start_local_value ⇒ Object



160
161
162
# File 'app/charts/good_job/performance_range.rb', line 160

def start_local_value
  local_value(start_time)
end

#time_series_binds ⇒ Object

Keep this order in sync with the $1/$2/$3 placeholders in PerformanceIndexChart.



165
166
167
168
169
170
171
# File 'app/charts/good_job/performance_range.rb', line 165

def time_series_binds
  [
    query_attribute("series_start_time", series_start_time, ActiveRecord::Type::DateTime.new),
    query_attribute("series_end_time", series_end_time, ActiveRecord::Type::DateTime.new),
    query_attribute("interval_seconds", interval_seconds, ActiveRecord::Type::Integer.new(limit: 8)),
  ]
end

#time_series_bucket_sql(column_name, start_expression:, interval_expression:) ⇒ Object



173
174
175
176
177
178
179
180
181
# File 'app/charts/good_job/performance_range.rb', line 173

def time_series_bucket_sql(column_name, start_expression:, interval_expression:)
  column_sql = GoodJob::Job.adapter_class.quote_column_name(column_name)

  <<~SQL.squish
    #{start_expression} +
    FLOOR(EXTRACT(EPOCH FROM (#{column_sql} - #{start_expression})) / #{interval_expression}) *
    #{interval_expression} * INTERVAL '1 second'
  SQL
end

#time_series_coordinate_count ⇒ Object



183
184
185
# File 'app/charts/good_job/performance_range.rb', line 183

def time_series_coordinate_count
  coordinate_count(interval_seconds)
end

#to_params ⇒ Object



187
188
189
# File 'app/charts/good_job/performance_range.rb', line 187

def to_params
  @canonical_params.dup
end