From 645461f62980d26a7116c0dd60821d56e64863d7 Mon Sep 17 00:00:00 2001 From: syasonik <syasonik@gitlab.com> Date: Wed, 1 Apr 2020 19:20:31 -0500 Subject: [PATCH] Add support for DB-less embeds In order to support metrics embeds for alerts from external alerting sources, we need a way to store that content, because the alert does not yet live in the database. This adds a class which allows post-processing for a url-provided embed json object. As dashboard content is already user-provided, this poses no further security concerns. --- .../projects/environments_controller.rb | 2 +- .../dashboard/transient_embed_service.rb | 35 +++++++++ changelogs/unreleased/sy-transient-embeds.yml | 5 ++ .../metrics/dashboard/service_selector.rb | 1 + spec/features/markdown/metrics_spec.rb | 30 ++++++++ .../dashboard/service_selector_spec.rb | 11 +++ .../dashboard/transient_embed_service_spec.rb | 72 +++++++++++++++++++ 7 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 app/services/metrics/dashboard/transient_embed_service.rb create mode 100644 changelogs/unreleased/sy-transient-embeds.yml create mode 100644 spec/services/metrics/dashboard/transient_embed_service_spec.rb diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 5c49fa842a4fda59..e51a5c7b84d12db8 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -222,7 +222,7 @@ def metrics_params def metrics_dashboard_params params - .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics) + .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics, :embed_json) .merge(dashboard_path: params[:dashboard], environment: environment) end diff --git a/app/services/metrics/dashboard/transient_embed_service.rb b/app/services/metrics/dashboard/transient_embed_service.rb new file mode 100644 index 0000000000000000..035707dceb9fab94 --- /dev/null +++ b/app/services/metrics/dashboard/transient_embed_service.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# Acts as a pass-through to allow embeddable dashboards to be +# generated based on external data, but still processed with the +# required attributes that allow the FE to render them appropriately. +# +# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. +module Metrics + module Dashboard + class TransientEmbedService < ::Metrics::Dashboard::BaseEmbedService + extend ::Gitlab::Utils::Override + + class << self + def valid_params?(params) + [ + embedded?(params[:embedded]), + params[:embed_json] + ].all? + end + end + + private + + override :get_raw_dashboard + def get_raw_dashboard + JSON.parse(params[:embed_json]) + end + + override :sequence + def sequence + [STAGES::EndpointInserter] + end + end + end +end diff --git a/changelogs/unreleased/sy-transient-embeds.yml b/changelogs/unreleased/sy-transient-embeds.yml new file mode 100644 index 0000000000000000..2e2189827bc9f232 --- /dev/null +++ b/changelogs/unreleased/sy-transient-embeds.yml @@ -0,0 +1,5 @@ +--- +title: Add support for database-independent embedded metric charts +merge_request: 28618 +author: +type: added diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb index b455f95537726f7b..49682da320cfa9b9 100644 --- a/lib/gitlab/metrics/dashboard/service_selector.rb +++ b/lib/gitlab/metrics/dashboard/service_selector.rb @@ -16,6 +16,7 @@ class << self ::Metrics::Dashboard::GitlabAlertEmbedService, ::Metrics::Dashboard::CustomMetricEmbedService, ::Metrics::Dashboard::GrafanaMetricEmbedService, + ::Metrics::Dashboard::TransientEmbedService, ::Metrics::Dashboard::DynamicEmbedService, ::Metrics::Dashboard::DefaultEmbedService, ::Metrics::Dashboard::SystemDashboardService, diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb index 0d8858a7afd9e5ab..dadb9571c545ace3 100644 --- a/spec/features/markdown/metrics_spec.rb +++ b/spec/features/markdown/metrics_spec.rb @@ -136,6 +136,36 @@ end end + context 'transient metrics embeds' do + let(:metrics_url) { urls.metrics_project_environment_url(project, environment, embed_json: embed_json) } + let(:title) { 'Important Metrics' } + let(:embed_json) do + { + panel_groups: [{ + panels: [{ + type: "line-graph", + title: title, + y_label: "metric", + metrics: [{ + query_range: "metric * 0.5 < 1" + }] + }] + }] + }.to_json + end + + before do + stub_any_prometheus_request_with_response + end + + it 'shows embedded metrics' do + visit project_issue_path(project, issue) + + expect(page).to have_css('div.prometheus-graph') + expect(page).to have_text(title) + end + end + def import_common_metrics ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute end diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb index 387baf1ee5364679..245c98cdd00cb496 100644 --- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -98,6 +98,17 @@ it { is_expected.to be Metrics::Dashboard::GrafanaMetricEmbedService } end + + context 'with the embed defined in the arguments' do + let(:arguments) do + { + embedded: true, + embed_json: '{}' + } + end + + it { is_expected.to be Metrics::Dashboard::TransientEmbedService } + end end end end diff --git a/spec/services/metrics/dashboard/transient_embed_service_spec.rb b/spec/services/metrics/dashboard/transient_embed_service_spec.rb new file mode 100644 index 0000000000000000..fddfbe1528118f5e --- /dev/null +++ b/spec/services/metrics/dashboard/transient_embed_service_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do + let_it_be(:project) { build(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + describe '.valid_params?' do + let(:params) { { embedded: 'true', embed_json: '{}' } } + + subject { described_class.valid_params?(params) } + + it { is_expected.to be_truthy } + + context 'missing embedded' do + let(:params) { { embed_json: '{}' } } + + it { is_expected.to be_falsey } + end + + context 'not embedded' do + let(:params) { { embedded: 'false', embed_json: '{}' } } + + it { is_expected.to be_falsey } + end + + context 'missing embed_json' do + let(:params) { { embedded: 'true' } } + + it { is_expected.to be_falsey } + end + end + + describe '#get_dashboard' do + let(:embed_json) do + { + panel_groups: [{ + panels: [{ + type: 'line-graph', + title: 'title', + y_label: 'y_label', + metrics: [{ + query_range: 'up', + label: 'y_label' + }] + }] + }] + }.to_json + end + let(:service_params) { [project, user, { environment: environment, embedded: 'true', embed_json: embed_json }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'valid embedded dashboard service response' + it_behaves_like 'raises error for users with insufficient permissions' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect_any_instance_of(described_class) + .to receive(:get_raw_dashboard) + .once + .and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + end +end -- GitLab