Commit 730cf5e3 authored by syasonik's avatar syasonik

Unit tests and refactor for operability

parent 74daa11f
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
class CommonMetricsInserter
class << self
# For each metric in the dashboard config, attempts to find a corresponding
# database record. If found, includes the record's id in the dashboard config.
def transform!(dashboard, _project)
common_metrics = ::PrometheusMetric.common
for_metrics(dashboard) do |metric|
metric_record = common_metrics.find { |m| m.identifier == metric[:id] }
metric[:metric_id] = metric_record.id if metric_record
end
end
private
def for_metrics(dashboard)
dashboard[:panel_groups].each do |panel_group|
panel_group[:panels].each do |panel|
panel[:metrics].each do |metric|
yield metric
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
class Processor
STAGES = [CommonMetricsInserter, ProjectMetricsInserter, Sorter].freeze
def initialize(dashboard, project)
@dashboard = dashboard.deep_transform_keys(&:to_sym)
@project = project
end
def process
STAGES.each { |stage| stage.transform!(@dashboard, @project) }
@dashboard.to_json
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
class ProjectMetricsInserter
DEFAULT_PANEL_TYPE = 'area-chart'
class << self
# Inserts project-specific metrics into the dashboard config.
# If there are no project-specific metrics, this will have no effect.
def transform!(dashboard, project)
project.prometheus_metrics.each do |project_metric|
group = find_or_create_panel_group(dashboard[:panel_groups], project_metric)
panel = find_or_create_panel(group[:panels], project_metric)
find_or_create_metric(panel[:metrics], project_metric)
end
end
private
# Looks for a panel_group corresponding to the provided metric object.
# If unavailable, inserts one.
# @param panel_groups [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_panel_group(panel_groups, metric)
panel_group = find_panel_group(panel_groups, metric)
return panel_group if panel_group
panel_group = new_panel_group(metric)
panel_groups << panel_group
panel_group
end
# Looks for a panel corresponding to the provided metric object.
# If unavailable, inserts one.
# @param panels [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_panel(panels, metric)
panel = find_panel(panels, metric)
return panel if panel
panel = new_panel(metric)
panels << panel
panel
end
# Looks for a metric corresponding to the provided metric object.
# If unavailable, inserts one.
# @param metrics [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_metric(metrics, metric)
target_metric = find_metric(metrics, metric)
return target_metric if target_metric
target_metric = new_metric(metric)
metrics << target_metric
target_metric
end
def find_panel_group(panel_groups, metric)
panel_groups.find { |group| group[:group] == metric.group_title }
end
def find_panel(panels, metric)
panel_identifiers = [DEFAULT_PANEL_TYPE, metric.title, metric.y_label]
panels.find { |panel| panel.values_at(:type, :title, :y_label) == panel_identifiers }
end
def find_metric(metrics, metric)
metrics.find { |m| m[:id] == metric.identifier }
end
def new_panel_group(metric)
{
group: metric.group_title,
priority: metric.priority,
panels: []
}
end
def new_panel(metric)
{
type: DEFAULT_PANEL_TYPE,
title: metric.title,
y_label: metric.y_label,
metrics: []
}
end
def new_metric(metric)
metric.queries.first.merge(metric_id: metric.id)
end
end
end
end
end
# frozen_string_literal: true
# Fetches the metrics dashboard layout and supplemented the output with DB info.
module Gitlab
module MetricsDashboard
class Service
SYSTEM_DASHBOARD_NAME = 'common_metrics'
SYSTEM_DASHBOARD_PATH = Rails.root.join('config', 'prometheus', "#{SYSTEM_DASHBOARD_NAME}.yml")
def initialize(project)
@project = project
end
# Returns a DB-supplemented json representation of a dashboard config file.
def get_dashboard
dashboard = Rails.cache.fetch(cache_key) { system_dashboard }
process_dashboard(dashboard)
end
private
# Returns the base metrics shipped with every GitLab service.
def system_dashboard
YAML.load_file(SYSTEM_DASHBOARD_PATH)
end
def cache_key
"metrics_dashboard_#{SYSTEM_DASHBOARD_NAME}"
end
def process_dashboard(dashboard)
Processor.new(dashboard, @project).process
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
class Sorter
class << self
def transform!(dashboard, _project)
sort_groups!(dashboard)
sort_panels!(dashboard)
end
private
# Sorts the groups in the dashboard by the :priority key
def sort_groups!(dashboard)
dashboard[:panel_groups] = dashboard[:panel_groups].sort_by { |group| -group[:priority].to_i }
end
# Sorts the panels in the dashboard by the :weight key
def sort_panels!(dashboard)
dashboard[:panel_groups].each do |group|
group[:panels] = group[:panels].sort_by { |panel| -panel[:weight].to_i }
end
end
end
end
end
end
......@@ -485,6 +485,30 @@ describe Projects::EnvironmentsController do
end
end
describe 'metrics_dashboard' do
context 'when prometheus endpoint is disabled' do
before do
stub_feature_flags(environment_metrics_use_prometheus_endpoint: false)
end
it 'responds with status code 403' do
get :metrics_dashboard, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when prometheus endpoint is enabled' do
it 'returns a json representation of the environment dashboard' do
get :metrics_dashboard, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to include('dashboard', 'order', 'panel_groups')
expect(json_response['panel_groups']).to all( include('group', 'priority', 'panels') )
end
end
end
describe 'GET #search' do
before do
create(:environment, name: 'staging', project: project)
......
dashboard: 'Test Dashboard'
order: 1
panel_groups:
- group: Group A
priority: 10
panels:
- title: "Super Chart A1"
type: "area-chart"
y_label: "y_label"
weight: 1
metrics:
- id: metric_a1
query_range: 'query'
unit: unit
label: Legend Label
- title: "Super Chart A2"
type: "area-chart"
y_label: "y_label"
weight: 2
metrics:
- id: metric_a2
query_range: 'query'
label: Legend Label
unit: unit
- group: Group B
priority: 1
panels:
- title: "Super Chart B"
type: "area-chart"
y_label: "y_label"
weight: 1
metrics:
- id: metric_b
query_range: 'query'
unit: unit
label: Legend Label
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::MetricsDashboard::Processor do
let(:project) { build(:project) }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics_dashboard/sample_dashboard.yml') }
describe 'process' do
let(:dashboard) { JSON.parse(described_class.new(dashboard_yml, project).process, symbolize_names: true) }
context 'when dashboard config corresponds to common metrics' do
let!(:common_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
it 'inserts metric ids into the config' do
target_metric = all_metrics.find { |metric| metric[:id] == 'metric_a1' }
expect(target_metric).to include(:metric_id)
end
end
context 'when the project has associated metrics' do
let!(:project_response_metric) { create(:prometheus_metric, project: project, group: :response) }
let!(:project_system_metric) { create(:prometheus_metric, project: project, group: :system) }
let!(:project_business_metric) { create(:prometheus_metric, project: project, group: :business) }
it 'includes project-specific metrics' do
expect(all_metrics).to include get_metric_details(project_system_metric)
expect(all_metrics).to include get_metric_details(project_response_metric)
expect(all_metrics).to include get_metric_details(project_business_metric)
end
it 'orders groups by priority and panels by weight' do
expected_metrics_order = [
'metric_a2', # group priority 10, panel weight 2
'metric_a1', # group priority 10, panel weight 1
'metric_b', # group priority 1, panel weight 1
project_business_metric.id, # group priority 0, panel weight nil (0)
project_response_metric.id, # group priority -5, panel weight nil (0)
project_system_metric.id, # group priority -10, panel weight nil (0)
]
actual_metrics_order = all_metrics.map { |m| m[:id] || m[:metric_id] }
expect(actual_metrics_order).to eq expected_metrics_order
end
end
end
private
def all_metrics
dashboard[:panel_groups].map do |group|
group[:panels].map { |panel| panel[:metrics] }
end.flatten
end
def get_metric_details(metric)
{
query_range: metric.query,
unit: metric.unit,
label: metric.legend,
metric_id: metric.id
}
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::MetricsDashboard::Service, :use_clean_rails_memory_store_caching do
let(:project) { build(:project) }
describe 'get_dashboard' do
it 'returns a json representation of the environment dashboard' do
dashboard = described_class.new(project).get_dashboard
json = JSON.parse(dashboard, symbolize_names: true)
expect(json).to include(:dashboard, :order, :panel_groups)
expect(json[:panel_groups]).to all( include(:group, :priority, :panels) )
end
it 'caches the dashboard for subsequent calls' do
expect(YAML).to receive(:load_file).once.and_call_original
described_class.new(project).get_dashboard
described_class.new(project).get_dashboard
end
end
end
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