summaryrefslogtreecommitdiff
path: root/app/finders/prometheus_metrics_finder.rb
blob: 84a071abbd54e66976c91c7213af1e8eabd60f74 (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
# frozen_string_literal: true

class PrometheusMetricsFinder
  ACCEPTED_PARAMS = [
    :project,
    :group,
    :title,
    :y_label,
    :identifier,
    :id,
    :common,
    :ordered
  ].freeze

  # Cautiously preferring a memoized class method over a constant
  # so that the DB connection is accessed after the class is loaded.
  def self.indexes
    @indexes ||= PrometheusMetric
      .connection
      .indexes(:prometheus_metrics)
      .map { |index| index.columns.map(&:to_sym) }
  end

  def initialize(params = {})
    @params = params.slice(*ACCEPTED_PARAMS)
  end

  # @return [PrometheusMetric, PrometheusMetric::ActiveRecord_Relation]
  def execute
    validate_params!

    metrics = by_project(::PrometheusMetric.all)
    metrics = by_group(metrics)
    metrics = by_title(metrics)
    metrics = by_y_label(metrics)
    metrics = by_common(metrics)
    metrics = by_ordered(metrics)
    metrics = by_identifier(metrics)
    metrics = by_id(metrics)

    metrics
  end

  private

  attr_reader :params

  def by_project(metrics)
    return metrics unless params[:project]

    metrics.for_project(params[:project])
  end

  def by_group(metrics)
    return metrics unless params[:group]

    metrics.for_group(params[:group])
  end

  def by_title(metrics)
    return metrics unless params[:title]

    metrics.for_title(params[:title])
  end

  def by_y_label(metrics)
    return metrics unless params[:y_label]

    metrics.for_y_label(params[:y_label])
  end

  def by_common(metrics)
    return metrics unless params[:common]

    metrics.common
  end

  def by_ordered(metrics)
    return metrics unless params[:ordered]

    metrics.ordered
  end

  def by_identifier(metrics)
    return metrics unless params[:identifier]

    metrics.for_identifier(params[:identifier])
  end

  def by_id(metrics)
    return metrics unless params[:id]

    metrics.id_in(params[:id])
  end

  def validate_params!
    validate_params_present!
    validate_id_params!
    validate_indexes!
  end

  # Ensure all provided params are supported
  def validate_params_present!
    raise ArgumentError, "Please provide one or more of: #{ACCEPTED_PARAMS}" if params.blank?
  end

  # Protect against the caller "finding" the wrong metric
  def validate_id_params!
    raise ArgumentError, 'Only one of :identifier, :id is permitted' if params[:identifier] && params[:id]
    raise ArgumentError, ':identifier must be scoped to a :project or :common' if params[:identifier] && !(params[:project] || params[:common])
  end

  # Protect against unaccounted-for, complex/slow queries.
  # This is not a hard and fast rule, but is meant to encourage
  # mindful inclusion of new queries.
  def validate_indexes!
    indexable_params = params.except(:ordered, :id, :project).keys
    indexable_params << :project_id if params[:project]
    indexable_params.sort!

    raise ArgumentError, "An index should exist for params: #{indexable_params}" unless appropriate_index?(indexable_params)
  end

  def appropriate_index?(indexable_params)
    return true if indexable_params.blank?

    self.class.indexes.any? { |index| (index - indexable_params).empty? }
  end
end