class Kennel::Models::Dashboard

Constants

DEFAULTS
READONLY_ATTRIBUTES
REQUEST_DEFAULTS
SUPPORTED_DEFINITION_OPTIONS
TRACKING_FIELD
WIDGET_DEFAULTS

Public Class Methods

api_resource() click to toggle source
# File lib/kennel/models/dashboard.rb, line 84
def api_resource
  "dashboard"
end
normalize(expected, actual) click to toggle source
Calls superclass method
# File lib/kennel/models/dashboard.rb, line 88
def normalize(expected, actual)
  super

  ignore_default expected, actual, DEFAULTS
  ignore_default expected, actual, reflow_type: "auto" if expected[:layout_type] == "ordered"

  widgets_pairs(expected, actual).each do |pair|
    pair.each { |w| sort_conditional_formats w }
    ignore_widget_defaults(*pair)
    ignore_request_defaults(*pair)
    pair.each { |widget| widget&.delete(:id) } # ids are kinda random so we always discard them
  end
end
parse_url(url) click to toggle source
# File lib/kennel/models/dashboard.rb, line 171
def self.parse_url(url)
  url[/\/dashboard\/([a-z\d-]+)/, 1]
end
url(id) click to toggle source
# File lib/kennel/models/dashboard.rb, line 167
def self.url(id)
  Utils.path_to_url "/dashboard/#{id}"
end

Private Class Methods

ignore_defaults(expected, actual, defaults) click to toggle source
# File lib/kennel/models/dashboard.rb, line 125
def ignore_defaults(expected, actual, defaults)
  [expected.size, actual.size].max.times do |i|
    ignore_default expected[i] || {}, actual[i] || {}, defaults
  end
end
ignore_request_defaults(expected, actual) click to toggle source

discard styles/conditional_formats/aggregator if nothing would change when we applied (both are default or nil)

# File lib/kennel/models/dashboard.rb, line 119
def ignore_request_defaults(expected, actual)
  a_r = actual&.dig(:definition, :requests) || []
  e_r = expected&.dig(:definition, :requests) || []
  ignore_defaults e_r, a_r, REQUEST_DEFAULTS
end
ignore_widget_defaults(expected, actual) click to toggle source
# File lib/kennel/models/dashboard.rb, line 111
def ignore_widget_defaults(expected, actual)
  types = [expected&.dig(:definition, :type), actual&.dig(:definition, :type)].uniq.compact
  return unless types.size == 1
  return unless defaults = WIDGET_DEFAULTS[types.first]
  ignore_default expected&.[](:definition) || {}, actual&.[](:definition) || {}, defaults
end
sort_conditional_formats(widget) click to toggle source

conditional_formats ordering is randomly changed by datadog, compare a stable ordering

# File lib/kennel/models/dashboard.rb, line 105
def sort_conditional_formats(widget)
  if formats = widget&.dig(:definition, :conditional_formats)
    widget[:definition][:conditional_formats] = formats.sort_by(&:hash)
  end
end
widgets_pairs(*pair) click to toggle source

expand nested widgets into expected/actual pairs for default resolution

a, e

-> [[a-w, e-w], [a-w1-w1, e-w1-w1], …]

# File lib/kennel/models/dashboard.rb, line 133
def widgets_pairs(*pair)
  result = [pair.map { |d| d[:widgets] || [] }]
  slots = result[0].map(&:size).max
  slots.times do |i|
    nested = pair.map { |d| d.dig(:widgets, i, :definition, :widgets) || [] }
    result << nested if nested.any?(&:any?)
  end
  result.flat_map { |a, e| [a.size, e.size].max.times.map { |i| [a[i], e[i]] } }
end

Public Instance Methods

as_json() click to toggle source
# File lib/kennel/models/dashboard.rb, line 144
def as_json
  return @json if @json
  all_widgets = render_definitions(definitions) + widgets
  expand_q all_widgets

  @json = {
    layout_type: layout_type,
    title: "#{title}#{LOCK}",
    description: description,
    template_variables: render_template_variables,
    template_variable_presets: template_variable_presets,
    widgets: all_widgets
  }

  @json[:reflow_type] = reflow_type if reflow_type # setting nil breaks create with "ordered"

  @json[:id] = id if id

  validate_json(@json) if validate

  @json
end
resolve_linked_tracking_ids!(id_map, **args) click to toggle source
# File lib/kennel/models/dashboard.rb, line 175
def resolve_linked_tracking_ids!(id_map, **args)
  widgets = as_json[:widgets].flat_map { |w| [w, *w.dig(:definition, :widgets) || []] }
  widgets.each do |widget|
    next unless definition = widget[:definition]
    case definition[:type]
    when "uptime"
      if ids = definition[:monitor_ids]
        definition[:monitor_ids] = ids.map do |id|
          tracking_id?(id) ? (resolve_link(id, :monitor, id_map, **args) || id) : id
        end
      end
    when "alert_graph"
      if (id = definition[:alert_id]) && tracking_id?(id)
        definition[:alert_id] = (resolve_link(id, :monitor, id_map, **args) || id).to_s
      end
    when "slo"
      if (id = definition[:slo_id]) && tracking_id?(id)
        definition[:slo_id] = (resolve_link(id, :slo, id_map, **args) || id).to_s
      end
    end
  end
end

Private Instance Methods

expand_q(widgets) click to toggle source

creates queries from metadata to avoid having to keep q and expression in sync

{q: :metadata, metadata: [{expression: “sum:bar”, alias_name: “foo”}, …], } -> {q: “sum:bar, …”, metadata: …, }

# File lib/kennel/models/dashboard.rb, line 208
def expand_q(widgets)
  widgets = widgets.flat_map { |w| w.dig(:definition, :widgets) || w } # expand groups
  widgets.each do |w|
    w.dig(:definition, :requests)&.each do |request|
      next unless request.is_a?(Hash) && request[:q] == :metadata
      request[:q] = request.fetch(:metadata).map { |m| m.fetch(:expression) }.join(", ")
    end
  end
end
render_definitions(definitions) click to toggle source
# File lib/kennel/models/dashboard.rb, line 228
def render_definitions(definitions)
  definitions.map do |title, type, display_type, queries, options = {}, too_many_args = nil|
    if title.is_a?(Hash) && !type
      title # user gave a full widget, just use it
    else
      # validate inputs
      if too_many_args || (!title || !type || !queries || !options.is_a?(Hash))
        raise ArgumentError, "Expected exactly 5 arguments for each definition (title, type, display_type, queries, options)"
      end
      if (SUPPORTED_DEFINITION_OPTIONS | options.keys) != SUPPORTED_DEFINITION_OPTIONS
        raise ArgumentError, "Supported options are: #{SUPPORTED_DEFINITION_OPTIONS.map(&:inspect).join(", ")}"
      end

      # build definition
      requests = Array(queries).map do |q|
        request = { q: q }
        request[:display_type] = display_type if display_type
        request
      end
      { definition: { title: title, type: type, requests: requests, **options } }
    end
  end
end
tracking_id?(id) click to toggle source
# File lib/kennel/models/dashboard.rb, line 200
def tracking_id?(id)
  id.is_a?(String) && id.include?(":")
end
validate_json(data) click to toggle source
# File lib/kennel/models/dashboard.rb, line 218
def validate_json(data)
  super

  validate_template_variables data

  # Avoid diff from datadog presets sorting.
  presets = data[:template_variable_presets]
  invalid! "template_variable_presets must be sorted by name" if presets && presets != presets.sort_by { |p| p[:name] }
end