Commit 6b0599ac authored by James Lopez's avatar James Lopez

Merge branch 'sy-embed-by-id' into 'master'

Create service and endpoint for embedding metrics by alert

See merge request gitlab-org/gitlab!23484
parents 235e03e2 046d0133
...@@ -46,10 +46,14 @@ module Metrics ...@@ -46,10 +46,14 @@ module Metrics
# Returns a new dashboard Hash, supplemented with DB info # Returns a new dashboard Hash, supplemented with DB info
def process_dashboard def process_dashboard
::Gitlab::Metrics::Dashboard::Processor ::Gitlab::Metrics::Dashboard::Processor
.new(project, raw_dashboard, sequence, params) .new(project, raw_dashboard, sequence, process_params)
.process .process
end end
def process_params
params
end
# @return [String] Relative filepath of the dashboard yml # @return [String] Relative filepath of the dashboard yml
def dashboard_path def dashboard_path
params[:dashboard_path] params[:dashboard_path]
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Projects module Projects
module Prometheus module Prometheus
class AlertsController < Projects::ApplicationController class AlertsController < Projects::ApplicationController
include MetricsDashboard
respond_to :json respond_to :json
protect_from_forgery except: [:notify] protect_from_forgery except: [:notify]
...@@ -134,6 +136,13 @@ module Projects ...@@ -134,6 +136,13 @@ module Projects
def prometheus_alerts def prometheus_alerts
project.prometheus_alerts.for_environment(params[:environment_id]) project.prometheus_alerts.for_environment(params[:environment_id])
end end
def metrics_dashboard_params
{
embedded: true,
prometheus_alert_id: params[:id].to_i
}
end
end end
end end
end end
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
module Projects module Projects
module Prometheus module Prometheus
# Find Prometheus alerts by +project+, by +environment+, or both. # Find Prometheus alerts by +project+, +environment+, +id+,
# or any combo thereof.
# #
# Optionally filter by +metric+. # Optionally filter by +metric+.
# #
...@@ -13,9 +14,9 @@ module Projects ...@@ -13,9 +14,9 @@ module Projects
# metric: PrometheusMetric | integer # metric: PrometheusMetric | integer
class AlertsFinder class AlertsFinder
def initialize(params = {}) def initialize(params = {})
unless params[:project] || params[:environment] unless params[:project] || params[:environment] || params[:id]
raise ArgumentError, raise ArgumentError,
'Please provide either :project or :environment, or both' 'Please provide one or more of the following params: :project, :environment, :id'
end end
@params = params @params = params
...@@ -28,6 +29,7 @@ module Projects ...@@ -28,6 +29,7 @@ module Projects
relation = by_project(PrometheusAlert) relation = by_project(PrometheusAlert)
relation = by_environment(relation) relation = by_environment(relation)
relation = by_metric(relation) relation = by_metric(relation)
relation = by_id(relation)
relation = ordered(relation) relation = ordered(relation)
relation relation
...@@ -55,6 +57,12 @@ module Projects ...@@ -55,6 +57,12 @@ module Projects
relation.for_metric(params[:metric]) relation.for_metric(params[:metric])
end end
def by_id(relation)
return relation unless params[:id]
relation.id_in(params[:id])
end
def ordered(relation) def ordered(relation)
relation.order_by('id_asc') relation.order_by('id_asc')
end end
......
# frozen_string_literal: true
# Responsible for returning an embed containing the specified
# metrics chart for an alert. Creates panels based on the
# matching metric stored in the database.
#
# Use Gitlab::Metrics::Dashboard::Finder to retrieve dashboards.
module Metrics
module Dashboard
class GitlabAlertEmbedService < ::Metrics::Dashboard::BaseEmbedService
include Gitlab::Utils::StrongMemoize
SEQUENCE = [STAGES::EndpointInserter].freeze
class << self
# Determines whether the provided params are sufficient
# to uniquely identify a panel composed of user-defined
# custom metrics from the DB.
def valid_params?(params)
[
params[:embedded],
params[:prometheus_alert_id].is_a?(Integer)
].all?
end
end
def raw_dashboard
panels_not_found!(alert_id: alert_id) unless alert && prometheus_metric
{ 'panel_groups' => [{ 'panels' => [panel] }] }
end
private
def allowed?
Ability.allowed?(current_user, :read_prometheus_alerts, project)
end
def alert_id
params[:prometheus_alert_id]
end
def alert
strong_memoize(:alert) do
Projects::Prometheus::AlertsFinder.new(id: alert_id).execute.first
end
end
def process_params
params.merge(environment: alert.environment)
end
def prometheus_metric
strong_memoize(:prometheus_metric) do
PrometheusMetricsFinder.new(id: alert.prometheus_metric_id).execute.first
end
end
def panel
{
title: prometheus_metric.title,
y_label: prometheus_metric.y_label,
metrics: [prometheus_metric.to_metric_hash]
}
end
def sequence
SEQUENCE
end
end
end
end
...@@ -104,6 +104,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -104,6 +104,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
namespace :prometheus do namespace :prometheus do
resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do
post :notify, on: :collection post :notify, on: :collection
member do
get :metrics_dashboard
end
end end
resources :metrics, constraints: { id: %r{[^\/]+} }, only: [] do resources :metrics, constraints: { id: %r{[^\/]+} }, only: [] do
......
...@@ -8,7 +8,8 @@ module EE ...@@ -8,7 +8,8 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
EE_SERVICES = [ EE_SERVICES = [
::Metrics::Dashboard::ClusterDashboardService ::Metrics::Dashboard::ClusterDashboardService,
::Metrics::Dashboard::GitlabAlertEmbedService
].freeze ].freeze
class_methods do class_methods do
......
...@@ -355,6 +355,30 @@ describe Projects::Prometheus::AlertsController do ...@@ -355,6 +355,30 @@ describe Projects::Prometheus::AlertsController do
it_behaves_like 'project non-specific metric', :not_found it_behaves_like 'project non-specific metric', :not_found
end end
describe 'GET #metrics_dashboard' do
let!(:alert) do
create(:prometheus_alert,
project: project,
environment: environment,
prometheus_metric: metric)
end
it 'returns a json object with the correct keys' do
get :metrics_dashboard, params: request_params(id: alert.id), format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.keys).to contain_exactly('dashboard', 'status')
end
it 'is the correct embed' do
get :metrics_dashboard, params: request_params(id: alert.id), format: :json
title = json_response['dashboard']['panel_groups'][0]['panels'][0]['title']
expect(title).to eq(metric.title)
end
end
def project_params(opts = {}) def project_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace, project_id: project) opts.reverse_merge(namespace_id: project.namespace, project_id: project)
end end
......
...@@ -99,6 +99,22 @@ describe Projects::Prometheus::AlertsFinder do ...@@ -99,6 +99,22 @@ describe Projects::Prometheus::AlertsFinder do
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
context 'with matching id' do
before do
params[:id] = alert.id
end
it { is_expected.to eq([alert]) }
end
context 'with a nil id' do
before do
params[:id] = nil
end
it { is_expected.to eq([alert, alert2]) }
end
end end
context 'with non-matching project-environment pair' do context 'with non-matching project-environment pair' do
...@@ -109,6 +125,30 @@ describe Projects::Prometheus::AlertsFinder do ...@@ -109,6 +125,30 @@ describe Projects::Prometheus::AlertsFinder do
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
context 'with id' do
before do
params[:id] = alert.id
end
it { is_expected.to eq([alert]) }
end
context 'with multiple ids' do
before do
params[:id] = [alert.id, other_alert.id]
end
it { is_expected.to eq([alert, other_alert]) }
end
context 'with non-matching id' do
before do
params[:id] = -5
end
it { is_expected.to be_empty }
end
end end
private private
...@@ -123,7 +163,7 @@ describe Projects::Prometheus::AlertsFinder do ...@@ -123,7 +163,7 @@ describe Projects::Prometheus::AlertsFinder do
it 'raises an error' do it 'raises an error' do
expect { subject } expect { subject }
.to raise_error(ArgumentError, 'Please provide either :project or :environment, or both') .to raise_error(ArgumentError, 'Please provide one or more of the following params: :project, :environment, :id')
end end
end end
end end
...@@ -13,5 +13,11 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do ...@@ -13,5 +13,11 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do
it { is_expected.to be Metrics::Dashboard::ClusterDashboardService } it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
end end
context 'when metrics embed is for an alert' do
let(:arguments) { { embedded: true, prometheus_alert_id: 5 } }
it { is_expected.to be Metrics::Dashboard::GitlabAlertEmbedService }
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::GitlabAlertEmbedService do
include MetricsDashboardHelpers
let_it_be(:alert) { create(:prometheus_alert) }
let_it_be(:project) { alert.project }
let_it_be(:user) { create(:user) }
let(:alert_id) { alert.id }
before do
project.add_maintainer(user)
project.clear_memoization(:licensed_feature_available)
end
describe '.valid_params?' do
let(:valid_params) do
{
embedded: true,
prometheus_alert_id: alert_id
}
end
subject { described_class.valid_params?(params) }
let(:params) { valid_params }
it { is_expected.to be_truthy }
context 'not embedded' do
let(:params) { valid_params.except(:embedded) }
it { is_expected.to be_falsey }
end
context 'missing alert id' do
let(:params) { valid_params.except(:prometheus_alert_id) }
it { is_expected.to be_falsey }
end
context 'missing alert id' do
let(:params) { valid_params.merge(prometheus_alert_id: 'none') }
it { is_expected.to be_falsey }
end
end
describe '#get_dashboard' do
let(:service_params) do
[
project,
user,
{
embedded: true,
prometheus_alert_id: alert_id
}
]
end
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'misconfigured dashboard service response', :unauthorized
context 'when alerting is available' do
before do
stub_licensed_features(prometheus_alerts: true)
end
it_behaves_like 'valid embedded dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it 'uses the metric info corresponding to the alert' do
result = service_call
metrics = result[:dashboard][:panel_groups][0][:panels][0][:metrics]
expect(metrics.length).to eq 1
expect(metrics.first[:metric_id]).to eq alert.prometheus_metric_id
end
context 'when the metric does not exist' do
let(:alert_id) { -4 }
it_behaves_like 'misconfigured dashboard service response', :not_found
end
it 'does not cache the unprocessed dashboard' do
expect(Gitlab::Metrics::Dashboard::Cache).not_to receive(:fetch)
described_class.new(*service_params).get_dashboard
end
end
end
end
...@@ -34,6 +34,8 @@ module Gitlab ...@@ -34,6 +34,8 @@ module Gitlab
# cluster, one of [:admin, :project, :group] # cluster, one of [:admin, :project, :group]
# @param options - grafana_url [String] URL pointing # @param options - grafana_url [String] URL pointing
# to a grafana dashboard panel # to a grafana dashboard panel
# @param options - prometheus_alert_id [Integer] ID of
# a PrometheusAlert. For dashboard embeds.
# @return [Hash] # @return [Hash]
def find(project, user, options = {}) def find(project, user, options = {})
service_for(options) service_for(options)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment