diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-15 15:06:12 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-15 15:06:12 +0300 |
commit | 3fc9a8e6957ddf75576dc63069c4c0249514499f (patch) | |
tree | 003e30463853843d6fb736a9396c7eb53a3dfc9a /app/finders | |
parent | e24153b0cb080b1b25076f8fd358b4273848f2e2 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/finders')
-rw-r--r-- | app/finders/prometheus_metrics_finder.rb | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/app/finders/prometheus_metrics_finder.rb b/app/finders/prometheus_metrics_finder.rb new file mode 100644 index 00000000000..84a071abbd5 --- /dev/null +++ b/app/finders/prometheus_metrics_finder.rb @@ -0,0 +1,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 |