Commit 5f6cc0d5 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch...

Merge branch 'mwaw/219398-metrics-extend-dashboard-yaml-definion-validation-when-viewing-the-file-to-exhaustive-list-' into 'master'

Resolve "Metrics: Extend dashboard YAML definion validation when viewing the file to exhaustive list of errors"

See merge request gitlab-org/gitlab!40103
parents 4111c635 476ee015
...@@ -14,7 +14,8 @@ module Resolvers ...@@ -14,7 +14,8 @@ module Resolvers
def resolve(**args) def resolve(**args)
return unless environment return unless environment
::PerformanceMonitoring::PrometheusDashboard.find_for(project: environment.project, user: context[:current_user], path: args[:path], options: { environment: environment }) ::PerformanceMonitoring::PrometheusDashboard
.find_for(project: environment.project, user: context[:current_user], path: args[:path], options: { environment: environment })
end end
end end
end end
......
...@@ -16,6 +16,13 @@ module Types ...@@ -16,6 +16,13 @@ module Types
field :annotations, Types::Metrics::Dashboards::AnnotationType.connection_type, null: true, field :annotations, Types::Metrics::Dashboards::AnnotationType.connection_type, null: true,
description: 'Annotations added to the dashboard', description: 'Annotations added to the dashboard',
resolver: Resolvers::Metrics::Dashboards::AnnotationResolver resolver: Resolvers::Metrics::Dashboards::AnnotationResolver
# In order to maintain backward compatibility we need to return NULL when there are no warnings
# and dashboard validation returns an empty array when there are no issues.
def schema_validation_warnings
warnings = object.schema_validation_warnings
warnings unless warnings.empty?
end
end end
# rubocop: enable Graphql/AuthorizeTypes # rubocop: enable Graphql/AuthorizeTypes
end end
......
...@@ -25,20 +25,30 @@ module BlobViewer ...@@ -25,20 +25,30 @@ module BlobViewer
private private
def parse_blob_data def parse_blob_data
yaml = ::Gitlab::Config::Loader::Yaml.new(blob.data).load_raw! if Feature.enabled?(:metrics_dashboard_exhaustive_validations, project)
exhaustive_metrics_dashboard_validation
else
old_metrics_dashboard_validation
end
end
def old_metrics_dashboard_validation
yaml = ::Gitlab::Config::Loader::Yaml.new(blob.data).load_raw!
::PerformanceMonitoring::PrometheusDashboard.from_json(yaml) ::PerformanceMonitoring::PrometheusDashboard.from_json(yaml)
nil []
rescue Gitlab::Config::Loader::FormatError => error rescue Gitlab::Config::Loader::FormatError => error
wrap_yml_syntax_error(error) ["YAML syntax: #{error.message}"]
rescue ActiveModel::ValidationError => invalid rescue ActiveModel::ValidationError => invalid
invalid.model.errors invalid.model.errors.messages.map { |messages| messages.join(': ') }
end end
def wrap_yml_syntax_error(error) def exhaustive_metrics_dashboard_validation
::PerformanceMonitoring::PrometheusDashboard.new.errors.tap do |errors| yaml = ::Gitlab::Config::Loader::Yaml.new(blob.data).load_raw!
errors.add(:'YAML syntax', error.message) Gitlab::Metrics::Dashboard::Validator
end .errors(yaml, dashboard_path: blob.path, project: project)
.map(&:message)
rescue Gitlab::Config::Loader::FormatError => error
[error.message]
end end
end end
end end
...@@ -53,14 +53,23 @@ module PerformanceMonitoring ...@@ -53,14 +53,23 @@ module PerformanceMonitoring
# This method is planned to be refactored as a part of https://gitlab.com/gitlab-org/gitlab/-/issues/219398 # This method is planned to be refactored as a part of https://gitlab.com/gitlab-org/gitlab/-/issues/219398
# implementation. For new existing logic was reused to faster deliver MVC # implementation. For new existing logic was reused to faster deliver MVC
def schema_validation_warnings def schema_validation_warnings
return run_custom_validation.map(&:message) if Feature.enabled?(:metrics_dashboard_exhaustive_validations, environment&.project)
self.class.from_json(reload_schema) self.class.from_json(reload_schema)
nil []
rescue Gitlab::Metrics::Dashboard::Errors::LayoutError => error
[error.message]
rescue ActiveModel::ValidationError => exception rescue ActiveModel::ValidationError => exception
exception.model.errors.map { |attr, error| "#{attr}: #{error}" } exception.model.errors.map { |attr, error| "#{attr}: #{error}" }
end end
private private
def run_custom_validation
Gitlab::Metrics::Dashboard::Validator
.errors(reload_schema, dashboard_path: path, project: environment&.project)
end
# dashboard finder methods are somehow limited, #find includes checking if # dashboard finder methods are somehow limited, #find includes checking if
# user is authorised to view selected dashboard, but modifies schema, which in some cases may # user is authorised to view selected dashboard, but modifies schema, which in some cases may
# cause false positives returned from validation, and #find_raw does not authorise users # cause false positives returned from validation, and #find_raw does not authorise users
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
= icon('warning fw') = icon('warning fw')
= _('Metrics Dashboard YAML definition is invalid:') = _('Metrics Dashboard YAML definition is invalid:')
%ul %ul
- viewer.errors.messages.each do |error| - viewer.errors.each do |error|
%li= error.join(': ') %li= error
= link_to _('Learn more'), help_page_path('operations/metrics/dashboards/index.md') = link_to _('Learn more'), help_page_path('operations/metrics/dashboards/index.md')
---
name: metrics_dashboard_exhaustive_validations
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40103
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241697
group: group::apm
type: development
default_enabled: false
\ No newline at end of file
...@@ -4,24 +4,22 @@ module Gitlab ...@@ -4,24 +4,22 @@ module Gitlab
module Metrics module Metrics
module Dashboard module Dashboard
module Validator module Validator
DASHBOARD_SCHEMA_PATH = 'lib/gitlab/metrics/dashboard/validator/schemas/dashboard.json'.freeze DASHBOARD_SCHEMA_PATH = Rails.root.join(*%w[lib gitlab metrics dashboard validator schemas dashboard.json]).freeze
class << self class << self
def validate(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil) def validate(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
errors = _validate(content, schema_path, dashboard_path: dashboard_path, project: project) errors(content, schema_path, dashboard_path: dashboard_path, project: project).empty?
errors.empty?
end end
def validate!(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil) def validate!(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
errors = _validate(content, schema_path, dashboard_path: dashboard_path, project: project) errors = errors(content, schema_path, dashboard_path: dashboard_path, project: project)
errors.empty? || raise(errors.first) errors.empty? || raise(errors.first)
end end
private def errors(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
Validator::Client
def _validate(content, schema_path, dashboard_path: nil, project: nil) .new(content, schema_path, dashboard_path: dashboard_path, project: project)
client = Validator::Client.new(content, schema_path, dashboard_path: dashboard_path, project: project) .execute
client.execute
end end
end end
end end
......
...@@ -46,7 +46,7 @@ module Gitlab ...@@ -46,7 +46,7 @@ module Gitlab
def validate_against_schema def validate_against_schema
schemer.validate(content).map do |error| schemer.validate(content).map do |error|
Errors::SchemaValidationError.new(error) ::Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError.new(error)
end end
end end
end end
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"properties": { "properties": {
"type": { "type": {
"type": "string", "type": "string",
"enum": ["area-chart", "anomaly-chart", "bar", "column", "stacked-column", "single-stat", "heatmap"], "enum": ["area-chart", "line-chart", "anomaly-chart", "bar", "column", "stacked-column", "single-stat", "heatmap", "gauge"],
"default": "area-chart" "default": "area-chart"
}, },
"title": { "type": "string" }, "title": { "type": "string" },
......
...@@ -564,35 +564,76 @@ RSpec.describe 'File blob', :js do ...@@ -564,35 +564,76 @@ RSpec.describe 'File blob', :js do
file_path: '.gitlab/dashboards/custom-dashboard.yml', file_path: '.gitlab/dashboards/custom-dashboard.yml',
file_content: file_content file_content: file_content
).execute ).execute
visit_blob('.gitlab/dashboards/custom-dashboard.yml')
end end
context 'valid dashboard file' do context 'with metrics_dashboard_exhaustive_validations feature flag off' do
let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) } before do
stub_feature_flags(metrics_dashboard_exhaustive_validations: false)
visit_blob('.gitlab/dashboards/custom-dashboard.yml')
end
it 'displays an auxiliary viewer' do context 'valid dashboard file' do
aggregate_failures do let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) }
# shows that dashboard yaml is valid
expect(page).to have_content('Metrics Dashboard YAML definition is valid.') it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that dashboard yaml is valid
expect(page).to have_content('Metrics Dashboard YAML definition is valid.')
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
context 'invalid dashboard file' do
let(:file_content) { "dashboard: 'invalid'" }
# shows a learn more link it 'displays an auxiliary viewer' do
expect(page).to have_link('Learn more') aggregate_failures do
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
expect(page).to have_content("panel_groups: should be an array of panel_groups objects")
# shows a learn more link
expect(page).to have_link('Learn more')
end
end end
end end
end end
context 'invalid dashboard file' do context 'with metrics_dashboard_exhaustive_validations feature flag on' do
let(:file_content) { "dashboard: 'invalid'" } before do
stub_feature_flags(metrics_dashboard_exhaustive_validations: true)
visit_blob('.gitlab/dashboards/custom-dashboard.yml')
end
it 'displays an auxiliary viewer' do context 'valid dashboard file' do
aggregate_failures do let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) }
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
expect(page).to have_content("panel_groups: should be an array of panel_groups objects")
# shows a learn more link it 'displays an auxiliary viewer' do
expect(page).to have_link('Learn more') aggregate_failures do
# shows that dashboard yaml is valid
expect(page).to have_content('Metrics Dashboard YAML definition is valid.')
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
context 'invalid dashboard file' do
let(:file_content) { "dashboard: 'invalid'" }
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
expect(page).to have_content("root is missing required keys: panel_groups")
# shows a learn more link
expect(page).to have_link('Learn more')
end
end end
end end
end end
......
dashboard: 'Test Dashboard'
panel_groups:
- group: Group B
panels:
- title: "Super Chart B"
type: "area-chart"
y_label: "y_label"
metrics:
- id: metric_b
query_range: 'query'
unit: unit
label: Legend Label
- group: Group A
...@@ -34,6 +34,17 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator::Errors do ...@@ -34,6 +34,17 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator::Errors do
it { is_expected.to eq 'root is missing required keys: one' } it { is_expected.to eq 'root is missing required keys: one' }
end end
context 'when there is type mismatch' do
%w(null string boolean integer number array object).each do |expected_type|
context "on type: #{expected_type}" do
let(:type) { expected_type }
let(:details) { nil }
it { is_expected.to eq "'property_name' at root is not of type: #{expected_type}" }
end
end
end
end end
context 'for nested object' do context 'for nested object' do
...@@ -52,8 +63,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator::Errors do ...@@ -52,8 +63,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator::Errors do
let(:type) { expected_type } let(:type) { expected_type }
let(:details) { nil } let(:details) { nil }
subject { described_class.new(error_hash).message }
it { is_expected.to eq "'property_name' at /nested_objects/0 is not of type: #{expected_type}" } it { is_expected.to eq "'property_name' at /nested_objects/0 is not of type: #{expected_type}" }
end end
end end
......
...@@ -143,4 +143,56 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator do ...@@ -143,4 +143,56 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator do
end end
end end
end end
describe '#errors' do
context 'valid dashboard schema' do
it 'returns no errors' do
expect(described_class.errors(valid_dashboard)).to eq []
end
context 'with duplicate metric_ids' do
it 'returns errors' do
expect(described_class.errors(duplicate_id_dashboard)).to eq [Gitlab::Metrics::Dashboard::Validator::Errors::DuplicateMetricIds.new]
end
end
context 'with dashboard_path and project' do
subject { described_class.errors(valid_dashboard, dashboard_path: 'test/path.yml', project: project) }
context 'with no conflicting metric identifiers in db' do
it { is_expected.to eq [] }
end
context 'with metric identifier present in current dashboard' do
before do
create(:prometheus_metric,
identifier: 'metric_a1',
dashboard_path: 'test/path.yml',
project: project
)
end
it { is_expected.to eq [] }
end
context 'with metric identifier present in another dashboard' do
before do
create(:prometheus_metric,
identifier: 'metric_a1',
dashboard_path: 'some/other/dashboard/path.yml',
project: project
)
end
it { is_expected.to eq [Gitlab::Metrics::Dashboard::Validator::Errors::DuplicateMetricIds.new] }
end
end
end
context 'invalid dashboard schema' do
it 'returns collection of validation errors' do
expect(described_class.errors(invalid_dashboard)).to all be_kind_of(Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError)
end
end
end
end end
...@@ -9,119 +9,228 @@ RSpec.describe BlobViewer::MetricsDashboardYml do ...@@ -9,119 +9,228 @@ RSpec.describe BlobViewer::MetricsDashboardYml do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:blob) { fake_blob(path: '.gitlab/dashboards/custom-dashboard.yml', data: data) } let(:blob) { fake_blob(path: '.gitlab/dashboards/custom-dashboard.yml', data: data) }
let(:sha) { sample_commit.id } let(:sha) { sample_commit.id }
let(:data) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
subject(:viewer) { described_class.new(blob) } subject(:viewer) { described_class.new(blob) }
context 'when the definition is valid' do context 'with metrics_dashboard_exhaustive_validations feature flag on' do
let(:data) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) } before do
stub_feature_flags(metrics_dashboard_exhaustive_validations: true)
end
context 'when the definition is valid' do
before do
allow(Gitlab::Metrics::Dashboard::Validator).to receive(:errors).and_return([])
end
describe '#valid?' do
it 'calls prepare! on the viewer' do
expect(viewer).to receive(:prepare!)
viewer.valid?
end
it 'processes dashboard yaml and returns true', :aggregate_failures do
yml = ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
expect_next_instance_of(::Gitlab::Config::Loader::Yaml, data) do |loader|
expect(loader).to receive(:load_raw!).and_call_original
end
expect(Gitlab::Metrics::Dashboard::Validator)
.to receive(:errors)
.with(yml, dashboard_path: '.gitlab/dashboards/custom-dashboard.yml', project: project)
.and_call_original
expect(viewer.valid?).to be true
end
end
describe '#errors' do
it 'returns empty array' do
expect(viewer.errors).to eq []
end
end
end
context 'when definition is invalid' do
let(:error) { ::Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError.new }
let(:data) do
<<~YAML
dashboard:
YAML
end
before do
allow(Gitlab::Metrics::Dashboard::Validator).to receive(:errors).and_return([error])
end
describe '#valid?' do describe '#valid?' do
it 'calls prepare! on the viewer' do it 'returns false' do
allow(PerformanceMonitoring::PrometheusDashboard).to receive(:from_json) expect(viewer.valid?).to be false
end
end
expect(viewer).to receive(:prepare!) describe '#errors' do
it 'returns validation errors' do
expect(viewer.errors).to eq ["Dashboard failed schema validation"]
end
end
end
viewer.valid? context 'when YAML syntax is invalid' do
let(:data) do
<<~YAML
dashboard: 'empty metrics'
panel_groups:
- group: 'Group Title'
YAML
end end
it 'returns true', :aggregate_failures do describe '#valid?' do
yml = ::Gitlab::Config::Loader::Yaml.new(data).load_raw! it 'returns false' do
expect(Gitlab::Metrics::Dashboard::Validator).not_to receive(:errors)
expect(viewer.valid?).to be false
end
end
expect_next_instance_of(::Gitlab::Config::Loader::Yaml, data) do |loader| describe '#errors' do
expect(loader).to receive(:load_raw!).and_call_original it 'returns validation errors' do
expect(viewer.errors).to all be_kind_of String
end end
expect(PerformanceMonitoring::PrometheusDashboard)
.to receive(:from_json)
.with(yml)
.and_call_original
expect(viewer.valid?).to be_truthy
end end
end end
describe '#errors' do context 'when YAML loader raises error' do
it 'returns nil' do let(:data) do
allow(PerformanceMonitoring::PrometheusDashboard).to receive(:from_json) <<~YAML
large yaml file
YAML
end
before do
allow(::Gitlab::Config::Loader::Yaml).to receive(:new)
.and_raise(::Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
end
expect(viewer.errors).to be nil it 'is invalid' do
expect(Gitlab::Metrics::Dashboard::Validator).not_to receive(:errors)
expect(viewer.valid?).to be false
end
it 'returns validation errors' do
expect(viewer.errors).to eq ['The parsed YAML is too big']
end end
end end
end end
context 'when definition is invalid' do context 'with metrics_dashboard_exhaustive_validations feature flag off' do
let(:error) { ActiveModel::ValidationError.new(PerformanceMonitoring::PrometheusDashboard.new.tap(&:validate)) } before do
let(:data) do stub_feature_flags(metrics_dashboard_exhaustive_validations: false)
<<~YAML
dashboard:
YAML
end end
describe '#valid?' do context 'when the definition is valid' do
it 'returns false' do describe '#valid?' do
expect(PerformanceMonitoring::PrometheusDashboard) before do
.to receive(:from_json).and_raise(error) allow(PerformanceMonitoring::PrometheusDashboard).to receive(:from_json)
end
it 'calls prepare! on the viewer' do
expect(viewer).to receive(:prepare!)
expect(viewer.valid?).to be_falsey viewer.valid?
end
it 'processes dashboard yaml and returns true', :aggregate_failures do
yml = ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
expect_next_instance_of(::Gitlab::Config::Loader::Yaml, data) do |loader|
expect(loader).to receive(:load_raw!).and_call_original
end
expect(PerformanceMonitoring::PrometheusDashboard)
.to receive(:from_json)
.with(yml)
.and_call_original
expect(viewer.valid?).to be true
end
end
describe '#errors' do
it 'returns empty array' do
expect(viewer.errors).to eq []
end
end end
end end
describe '#errors' do context 'when definition is invalid' do
it 'returns validation errors' do let(:error) { ActiveModel::ValidationError.new(PerformanceMonitoring::PrometheusDashboard.new.tap(&:validate)) }
allow(PerformanceMonitoring::PrometheusDashboard) let(:data) do
.to receive(:from_json).and_raise(error) <<~YAML
dashboard:
YAML
end
describe '#valid?' do
it 'returns false' do
expect(PerformanceMonitoring::PrometheusDashboard)
.to receive(:from_json).and_raise(error)
expect(viewer.errors).to be error.model.errors expect(viewer.valid?).to be false
end
end
describe '#errors' do
it 'returns validation errors' do
allow(PerformanceMonitoring::PrometheusDashboard)
.to receive(:from_json).and_raise(error)
expect(viewer.errors).to eq error.model.errors.messages.map { |messages| messages.join(': ') }
end
end end
end end
end
context 'when YAML syntax is invalid' do context 'when YAML syntax is invalid' do
let(:data) do let(:data) do
<<~YAML <<~YAML
dashboard: 'empty metrics' dashboard: 'empty metrics'
panel_groups: panel_groups:
- group: 'Group Title' - group: 'Group Title'
YAML YAML
end
describe '#valid?' do
it 'returns false' do
expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:from_json)
expect(viewer.valid?).to be_falsey
end end
end
describe '#errors' do describe '#valid?' do
it 'returns validation errors' do it 'returns false' do
yaml_wrapped_errors = { 'YAML syntax': ["(<unknown>): did not find expected key while parsing a block mapping at line 1 column 1"] } expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:from_json)
expect(viewer.valid?).to be false
end
end
expect(viewer.errors).to be_kind_of ActiveModel::Errors describe '#errors' do
expect(viewer.errors.messages).to eql(yaml_wrapped_errors) it 'returns validation errors' do
expect(viewer.errors).to eq ["YAML syntax: (<unknown>): did not find expected key while parsing a block mapping at line 1 column 1"]
end
end end
end end
end
context 'when YAML loader raises error' do context 'when YAML loader raises error' do
let(:data) do let(:data) do
<<~YAML <<~YAML
large yaml file large yaml file
YAML YAML
end end
before do
allow(::Gitlab::Config::Loader::Yaml).to receive(:new)
.and_raise(::Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
end
it 'is invalid' do before do
expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:from_json) allow(::Gitlab::Config::Loader::Yaml).to(
expect(viewer.valid?).to be(false) receive(:new).and_raise(::Gitlab::Config::Loader::Yaml::DataTooLargeError, 'The parsed YAML is too big')
end )
end
it 'returns validation errors' do it 'is invalid' do
yaml_wrapped_errors = { 'YAML syntax': ["The parsed YAML is too big"] } expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:from_json)
expect(viewer.valid?).to be false
end
expect(viewer.errors).to be_kind_of(ActiveModel::Errors) it 'returns validation errors' do
expect(viewer.errors.messages).to eq(yaml_wrapped_errors) expect(viewer.errors).to eq ["YAML syntax: The parsed YAML is too big"]
end
end end
end end
end end
...@@ -219,20 +219,93 @@ RSpec.describe PerformanceMonitoring::PrometheusDashboard do ...@@ -219,20 +219,93 @@ RSpec.describe PerformanceMonitoring::PrometheusDashboard do
end end
describe '#schema_validation_warnings' do describe '#schema_validation_warnings' do
context 'when schema is valid' do let(:environment) { create(:environment, project: project) }
it 'returns nil' do let(:path) { '.gitlab/dashboards/test.yml' }
expect(described_class).to receive(:from_json) let(:project) { create(:project, :repository, :custom_repo, files: { path => dashboard_schema.to_yaml }) }
expect(described_class.new.schema_validation_warnings).to be_nil
subject(:schema_validation_warnings) { described_class.new(dashboard_schema.merge(path: path, environment: environment)).schema_validation_warnings }
before do
allow(Gitlab::Metrics::Dashboard::Finder).to receive(:find_raw).with(project, dashboard_path: path).and_call_original
end
context 'metrics_dashboard_exhaustive_validations is on' do
before do
stub_feature_flags(metrics_dashboard_exhaustive_validations: true)
end
context 'when schema is valid' do
let(:dashboard_schema) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
it 'returns empty array' do
expect(Gitlab::Metrics::Dashboard::Validator).to receive(:errors).with(dashboard_schema, dashboard_path: path, project: project).and_return([])
expect(schema_validation_warnings).to eq []
end
end
context 'when schema is invalid' do
let(:dashboard_schema) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml')) }
it 'returns array with errors messages' do
error = ::Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError.new
expect(Gitlab::Metrics::Dashboard::Validator).to receive(:errors).with(dashboard_schema, dashboard_path: path, project: project).and_return([error])
expect(schema_validation_warnings).to eq [error.message]
end
end
context 'when YAML has wrong syntax' do
let(:project) { create(:project, :repository, :custom_repo, files: { path => fixture_file('lib/gitlab/metrics/dashboard/broken_yml_syntax.yml') }) }
subject(:schema_validation_warnings) { described_class.new(path: path, environment: environment).schema_validation_warnings }
it 'returns array with errors messages' do
expect(Gitlab::Metrics::Dashboard::Validator).not_to receive(:errors)
expect(schema_validation_warnings).to eq ['Invalid yaml']
end
end end
end end
context 'when schema is invalid' do context 'metrics_dashboard_exhaustive_validations is off' do
it 'returns array with errors messages' do before do
instance = described_class.new stub_feature_flags(metrics_dashboard_exhaustive_validations: false)
instance.errors.add(:test, 'test error') end
context 'when schema is valid' do
let(:dashboard_schema) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
it 'returns empty array' do
expect(described_class).to receive(:from_json).with(dashboard_schema)
expect(schema_validation_warnings).to eq []
end
end
context 'when schema is invalid' do
let(:dashboard_schema) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml')) }
it 'returns array with errors messages' do
instance = described_class.new
instance.errors.add(:test, 'test error')
expect(described_class).to receive(:from_json).and_raise(ActiveModel::ValidationError.new(instance))
expect(described_class.new.schema_validation_warnings).to eq ['test: test error']
end
end
expect(described_class).to receive(:from_json).and_raise(ActiveModel::ValidationError.new(instance)) context 'when YAML has wrong syntax' do
expect(described_class.new.schema_validation_warnings).to eq ['test: test error'] let(:project) { create(:project, :repository, :custom_repo, files: { path => fixture_file('lib/gitlab/metrics/dashboard/broken_yml_syntax.yml') }) }
subject(:schema_validation_warnings) { described_class.new(path: path, environment: environment).schema_validation_warnings }
it 'returns array with errors messages' do
expect(described_class).not_to receive(:from_json)
expect(schema_validation_warnings).to eq ['Invalid yaml']
end
end end
end end
end end
......
...@@ -7,7 +7,7 @@ RSpec.describe 'Getting Metrics Dashboard' do ...@@ -7,7 +7,7 @@ RSpec.describe 'Getting Metrics Dashboard' do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let!(:environment) { create(:environment, project: project) } let(:environment) { create(:environment, project: project) }
let(:query) do let(:query) do
graphql_query_for( graphql_query_for(
...@@ -25,73 +25,156 @@ RSpec.describe 'Getting Metrics Dashboard' do ...@@ -25,73 +25,156 @@ RSpec.describe 'Getting Metrics Dashboard' do
) )
end end
context 'for anonymous user' do context 'with metrics_dashboard_exhaustive_validations feature flag off' do
before do before do
post_graphql(query, current_user: current_user) stub_feature_flags(metrics_dashboard_exhaustive_validations: false)
end end
context 'requested dashboard is available' do context 'for anonymous user' do
let(:path) { 'config/prometheus/common_metrics.yml' } before do
post_graphql(query, current_user: current_user)
end
context 'requested dashboard is available' do
let(:path) { 'config/prometheus/common_metrics.yml' }
it_behaves_like 'a working graphql query'
it 'returns nil' do
dashboard = graphql_data.dig('project', 'environments', 'nodes')
expect(dashboard).to be_nil
end
end
end
context 'for user with developer access' do
before do
project.add_developer(current_user)
post_graphql(query, current_user: current_user)
end
context 'requested dashboard is available' do
let(:path) { 'config/prometheus/common_metrics.yml' }
it_behaves_like 'a working graphql query'
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil)
end
context 'invalid dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' }
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
end
end
context 'empty dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' }
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) }
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
it_behaves_like 'a working graphql query' expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
end
end
end
context 'requested dashboard can not be found' do
let(:path) { 'config/prometheus/i_am_not_here.yml' }
it 'returns nil' do it_behaves_like 'a working graphql query'
dashboard = graphql_data.dig('project', 'environments', 'nodes')
expect(dashboard).to be_nil it 'returns nil' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to be_nil
end
end end
end end
end end
context 'for user with developer access' do context 'with metrics_dashboard_exhaustive_validations feature flag on' do
before do before do
project.add_developer(current_user) stub_feature_flags(metrics_dashboard_exhaustive_validations: true)
post_graphql(query, current_user: current_user)
end end
context 'requested dashboard is available' do context 'for anonymous user' do
let(:path) { 'config/prometheus/common_metrics.yml' } before do
post_graphql(query, current_user: current_user)
end
context 'requested dashboard is available' do
let(:path) { 'config/prometheus/common_metrics.yml' }
it_behaves_like 'a working graphql query'
it_behaves_like 'a working graphql query' it 'returns nil' do
dashboard = graphql_data.dig('project', 'environments', 'nodes')
it 'returns metrics dashboard' do expect(dashboard).to be_nil
dashboard = graphql_data.dig('project', 'environments', 'nodes')[0]['metricsDashboard'] end
end
end
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil) context 'for user with developer access' do
before do
project.add_developer(current_user)
post_graphql(query, current_user: current_user)
end end
context 'invalid dashboard' do context 'requested dashboard is available' do
let(:path) { '.gitlab/dashboards/metrics.yml' } let(:path) { 'config/prometheus/common_metrics.yml' }
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
it_behaves_like 'a working graphql query'
it 'returns metrics dashboard' do it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"]) expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil)
end end
end
context 'empty dashboard' do context 'invalid dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' } let(:path) { '.gitlab/dashboards/metrics.yml' }
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) } let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
it 'returns metrics dashboard' do it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"]) expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["root is missing required keys: panel_groups"])
end
end
context 'empty dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' }
let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) }
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["root is missing required keys: dashboard, panel_groups"])
end
end end
end end
end
context 'requested dashboard can not be found' do context 'requested dashboard can not be found' do
let(:path) { 'config/prometheus/i_am_not_here.yml' } let(:path) { 'config/prometheus/i_am_not_here.yml' }
it_behaves_like 'a working graphql query' it_behaves_like 'a working graphql query'
it 'returns nil' do it 'returns nil' do
dashboard = graphql_data.dig('project', 'environments', 'nodes')[0]['metricsDashboard'] dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
expect(dashboard).to be_nil expect(dashboard).to be_nil
end
end end
end 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