2019-07-31 22:56:46 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
describe Gitlab::Metrics::Dashboard::Processor do
|
|
|
|
let(:project) { build(:project) }
|
2019-09-04 21:01:54 +05:30
|
|
|
let(:environment) { create(:environment, project: project) }
|
2019-07-31 22:56:46 +05:30
|
|
|
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
|
|
|
|
|
|
|
|
describe 'process' do
|
2019-12-04 20:38:33 +05:30
|
|
|
let(:sequence) do
|
|
|
|
[
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
|
2020-04-22 19:07:51 +05:30
|
|
|
Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter,
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
|
2019-12-04 20:38:33 +05:30
|
|
|
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
|
2020-04-22 19:07:51 +05:30
|
|
|
Gitlab::Metrics::Dashboard::Stages::Sorter,
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
|
2020-06-23 00:09:42 +05:30
|
|
|
Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::UrlValidator
|
2019-12-04 20:38:33 +05:30
|
|
|
]
|
|
|
|
end
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2019-12-04 20:38:33 +05:30
|
|
|
let(:process_params) { [project, dashboard_yml, sequence, { environment: environment }] }
|
|
|
|
let(:dashboard) { described_class.new(*process_params).process }
|
2019-07-31 22:56:46 +05:30
|
|
|
|
2019-09-04 21:01:54 +05:30
|
|
|
it 'includes a path for the prometheus endpoint with each metric' do
|
|
|
|
expect(all_metrics).to satisfy_all do |metric|
|
|
|
|
metric[:prometheus_endpoint_path] == prometheus_path(metric[:query_range])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
it 'includes an id for each dashboard panel' do
|
|
|
|
expect(all_panels).to satisfy_all do |panel|
|
|
|
|
panel[:id].present?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
it 'includes boolean to indicate if panel group has custom metrics' do
|
|
|
|
expect(dashboard[:panel_groups]).to all(include( { has_custom_metrics: boolean } ))
|
|
|
|
end
|
|
|
|
|
2019-12-26 22:10:19 +05:30
|
|
|
context 'when the dashboard is not present' do
|
|
|
|
let(:dashboard_yml) { nil }
|
|
|
|
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(dashboard).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
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)
|
|
|
|
expect(target_metric[:metric_id]).to eq(common_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_b', # group priority 10, panel weight 1
|
|
|
|
'metric_a2', # group priority 1, panel weight 2
|
|
|
|
'metric_a1', # 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)
|
2020-03-13 15:44:24 +05:30
|
|
|
project_system_metric.id # group priority -10, panel weight nil (0)
|
2019-07-31 22:56:46 +05:30
|
|
|
]
|
|
|
|
actual_metrics_order = all_metrics.map { |m| m[:id] || m[:metric_id] }
|
|
|
|
|
|
|
|
expect(actual_metrics_order).to eq expected_metrics_order
|
|
|
|
end
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
context 'when the project has multiple metrics in the same group' do
|
|
|
|
let!(:project_response_metric) { create(:prometheus_metric, project: project, group: :response) }
|
|
|
|
let!(:project_response_metric_2) { create(:prometheus_metric, project: project, group: :response) }
|
|
|
|
|
|
|
|
it 'includes multiple metrics' do
|
|
|
|
expect(all_metrics).to include get_metric_details(project_response_metric)
|
|
|
|
expect(all_metrics).to include get_metric_details(project_response_metric_2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
context 'when the dashboard should not include project metrics' do
|
2019-12-04 20:38:33 +05:30
|
|
|
let(:sequence) do
|
|
|
|
[
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
|
|
|
|
Gitlab::Metrics::Dashboard::Stages::Sorter
|
|
|
|
]
|
|
|
|
end
|
|
|
|
let(:dashboard) { described_class.new(*process_params).process }
|
2019-07-31 22:56:46 +05:30
|
|
|
|
|
|
|
it 'includes only dashboard metrics' do
|
|
|
|
metrics = all_metrics.map { |m| m[:id] }
|
|
|
|
|
|
|
|
expect(metrics.length).to be(3)
|
|
|
|
expect(metrics).to eq %w(metric_b metric_a2 metric_a1)
|
|
|
|
end
|
|
|
|
end
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
context 'when sample_metrics are requested' do
|
|
|
|
let(:process_params) { [project, dashboard_yml, sequence, { environment: environment, sample_metrics: true }] }
|
|
|
|
|
|
|
|
it 'includes a sample metrics path for the prometheus endpoint with each metric' do
|
|
|
|
expect(all_metrics).to satisfy_all do |metric|
|
|
|
|
metric[:prometheus_endpoint_path] == sample_metrics_path(metric[:id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-07-31 22:56:46 +05:30
|
|
|
end
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
context 'when the dashboard references persisted metrics with alerts' do
|
|
|
|
let!(:alert) do
|
|
|
|
create(
|
|
|
|
:prometheus_alert,
|
|
|
|
environment: environment,
|
|
|
|
project: project,
|
|
|
|
prometheus_metric: persisted_metric
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples_for 'has saved alerts' do
|
|
|
|
it 'includes an alert path' do
|
|
|
|
target_metric = all_metrics.find { |metric| metric[:metric_id] == persisted_metric.id }
|
|
|
|
|
|
|
|
expect(target_metric).to be_a Hash
|
|
|
|
expect(target_metric).to include(:alert_path)
|
|
|
|
expect(target_metric[:alert_path]).to include(
|
|
|
|
project.path,
|
|
|
|
persisted_metric.id.to_s,
|
|
|
|
environment.id.to_s
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'that are shared across projects' do
|
|
|
|
let!(:persisted_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
|
|
|
|
|
|
|
|
it_behaves_like 'has saved alerts'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the project has associated metrics' do
|
|
|
|
let!(:persisted_metric) { create(:prometheus_metric, project: project, group: :business) }
|
|
|
|
|
|
|
|
it_behaves_like 'has saved alerts'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there are no alerts' do
|
|
|
|
let!(:persisted_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
|
|
|
|
|
|
|
|
it 'does not insert an alert_path' do
|
|
|
|
target_metric = all_metrics.find { |metric| metric[:metric_id] == persisted_metric.id }
|
|
|
|
|
|
|
|
expect(target_metric).to be_a Hash
|
|
|
|
expect(target_metric).not_to include(:alert_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
shared_examples_for 'errors with message' do |expected_message|
|
|
|
|
it 'raises a DashboardLayoutError' do
|
2019-12-04 20:38:33 +05:30
|
|
|
error_class = Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError
|
2019-07-31 22:56:46 +05:30
|
|
|
|
|
|
|
expect { dashboard }.to raise_error(error_class, expected_message)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the dashboard is missing panel_groups' do
|
|
|
|
let(:dashboard_yml) { {} }
|
|
|
|
|
|
|
|
it_behaves_like 'errors with message', 'Top-level key :panel_groups must be an array'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the dashboard contains a panel_group which is missing panels' do
|
|
|
|
let(:dashboard_yml) { { panel_groups: [{}] } }
|
|
|
|
|
|
|
|
it_behaves_like 'errors with message', 'Each "panel_group" must define an array :panels'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the dashboard contains a panel which is missing metrics' do
|
|
|
|
let(:dashboard_yml) { { panel_groups: [{ panels: [{}] }] } }
|
|
|
|
|
|
|
|
it_behaves_like 'errors with message', 'Each "panel" must define an array :metrics'
|
|
|
|
end
|
2019-09-04 21:01:54 +05:30
|
|
|
|
|
|
|
context 'when the dashboard contains a metric which is missing a query' do
|
|
|
|
let(:dashboard_yml) { { panel_groups: [{ panels: [{ metrics: [{}] }] }] } }
|
|
|
|
|
|
|
|
it_behaves_like 'errors with message', 'Each "metric" must define one of :query or :query_range'
|
|
|
|
end
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
describe 'validating links' do
|
|
|
|
context 'when the links contain a blocked url' do
|
|
|
|
let(:dashboard_yml_links) do
|
|
|
|
[{ 'url' => 'http://1.1.1.1.1' }, { 'url' => 'https://gitlab.com' }]
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:expected) do
|
|
|
|
[{ url: '' }, { url: 'https://gitlab.com' }]
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
|
|
|
|
dashboard_yml['links'] = dashboard_yml_links
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'replaces the blocked url with an empty string' do
|
|
|
|
expect(dashboard[:links]).to eq(expected)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-07-31 22:56:46 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def all_metrics
|
2020-04-22 19:07:51 +05:30
|
|
|
all_panels.flat_map { |panel| panel[:metrics] }
|
|
|
|
end
|
|
|
|
|
|
|
|
def all_panels
|
|
|
|
dashboard[:panel_groups].flat_map { |group| group[:panels] }
|
2019-07-31 22:56:46 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def get_metric_details(metric)
|
|
|
|
{
|
|
|
|
query_range: metric.query,
|
|
|
|
unit: metric.unit,
|
|
|
|
label: metric.legend,
|
2019-09-04 21:01:54 +05:30
|
|
|
metric_id: metric.id,
|
2020-03-13 15:44:24 +05:30
|
|
|
prometheus_endpoint_path: prometheus_path(metric.query),
|
|
|
|
edit_path: edit_metric_path(metric)
|
2019-07-31 22:56:46 +05:30
|
|
|
}
|
|
|
|
end
|
2019-09-04 21:01:54 +05:30
|
|
|
|
|
|
|
def prometheus_path(query)
|
|
|
|
Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
|
|
|
|
project,
|
|
|
|
environment,
|
|
|
|
proxy_path: :query_range,
|
|
|
|
query: query
|
|
|
|
)
|
|
|
|
end
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
def sample_metrics_path(metric)
|
|
|
|
Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
|
|
|
|
project,
|
|
|
|
environment,
|
|
|
|
identifier: metric
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def edit_metric_path(metric)
|
|
|
|
Gitlab::Routing.url_helpers.edit_project_prometheus_metric_path(
|
|
|
|
project,
|
|
|
|
metric.id
|
|
|
|
)
|
|
|
|
end
|
2019-07-31 22:56:46 +05:30
|
|
|
end
|