Commit 61c70eb6 authored by syasonik's avatar syasonik

Refactors metric embeds to ease adding embed types

There will soon be two new embed types: cluster embeds and
alert embeds. We want to make it as easier to add those
new classes as possible without duplicate work. This
refactor pulls out some shared examples and allows for
future easy ee overrides.
parent 6dbac390
...@@ -20,6 +20,7 @@ module Banzai ...@@ -20,6 +20,7 @@ module Banzai
# the cost of doing a full regex match. # the cost of doing a full regex match.
def xpath_search def xpath_search
"descendant-or-self::a[contains(@href,'metrics') and \ "descendant-or-self::a[contains(@href,'metrics') and \
contains(@href,'environments') and \
starts-with(@href, '#{Gitlab.config.gitlab.url}')]" starts-with(@href, '#{Gitlab.config.gitlab.url}')]"
end end
......
...@@ -9,8 +9,8 @@ module Banzai ...@@ -9,8 +9,8 @@ module Banzai
METRICS_CSS_CLASS = '.js-render-metrics' METRICS_CSS_CLASS = '.js-render-metrics'
EMBED_LIMIT = 100 EMBED_LIMIT = 100
URL = Gitlab::Metrics::Dashboard::Url
Route = Struct.new(:regex, :permission)
Embed = Struct.new(:project_path, :permission) Embed = Struct.new(:project_path, :permission)
# Finds all embeds based on the css class the FE # Finds all embeds based on the css class the FE
...@@ -59,14 +59,28 @@ module Banzai ...@@ -59,14 +59,28 @@ module Banzai
embed = Embed.new embed = Embed.new
url = node.attribute('data-dashboard-url').to_s url = node.attribute('data-dashboard-url').to_s
set_path_and_permission(embed, url, URL.metrics_regex, :read_environment) permissions_by_route.each do |route|
set_path_and_permission(embed, url, URL.grafana_regex, :read_project) unless embed.permission set_path_and_permission(embed, url, route.regex, route.permission) unless embed.permission
end
embeds[node] = embed if embed.permission embeds[node] = embed if embed.permission
end end
end end
end end
def permissions_by_route
[
Route.new(
::Gitlab::Metrics::Dashboard::Url.metrics_regex,
:read_environment
),
Route.new(
::Gitlab::Metrics::Dashboard::Url.grafana_regex,
:read_project
)
]
end
# Attempts to determine the path and permission attributes # Attempts to determine the path and permission attributes
# of a url based on expected dashboard url formats and # of a url based on expected dashboard url formats and
# sets the attributes on an Embed object # sets the attributes on an Embed object
......
...@@ -29,8 +29,7 @@ module Banzai ...@@ -29,8 +29,7 @@ module Banzai
Filter::AudioLinkFilter, Filter::AudioLinkFilter,
Filter::ImageLazyLoadFilter, Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter, Filter::ImageLinkFilter,
Filter::InlineMetricsFilter, *metrics_filters,
Filter::InlineGrafanaMetricsFilter,
Filter::TableOfContentsFilter, Filter::TableOfContentsFilter,
Filter::TableOfContentsTagFilter, Filter::TableOfContentsTagFilter,
Filter::AutolinkFilter, Filter::AutolinkFilter,
...@@ -48,6 +47,13 @@ module Banzai ...@@ -48,6 +47,13 @@ module Banzai
] ]
end end
def self.metrics_filters
[
Filter::InlineMetricsFilter,
Filter::InlineGrafanaMetricsFilter
]
end
def self.reference_filters def self.reference_filters
[ [
Filter::UserReferenceFilter, Filter::UserReferenceFilter,
......
...@@ -8,40 +8,26 @@ describe Banzai::Filter::InlineGrafanaMetricsFilter do ...@@ -8,40 +8,26 @@ describe Banzai::Filter::InlineGrafanaMetricsFilter do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:grafana_integration) { create(:grafana_integration, project: project) } let_it_be(:grafana_integration) { create(:grafana_integration, project: project) }
let(:input) { %(<a href="#{url}">example</a>) } let(:input) { %(<a href="#{trigger_url}">example</a>) }
let(:doc) { filter(input) } let(:doc) { filter(input) }
let(:url) { grafana_integration.grafana_url + dashboard_path }
let(:dashboard_path) do let(:dashboard_path) do
'/d/XDaNK6amz/gitlab-omnibus-redis' \ '/d/XDaNK6amz/gitlab-omnibus-redis' \
'?from=1570397739557&to=1570484139557' \ '?from=1570397739557&to=1570484139557' \
'&var-instance=All&panelId=14' '&var-instance=All&panelId=14'
end end
it 'appends a metrics charts placeholder with dashboard url after metrics links' do let(:trigger_url) { grafana_integration.grafana_url + dashboard_path }
node = doc.at_css('.js-render-metrics') let(:dashboard_url) do
expect(node).to be_present urls.project_grafana_api_metrics_dashboard_url(
dashboard_url = urls.project_grafana_api_metrics_dashboard_url(
project, project,
embedded: true, embedded: true,
grafana_url: url, grafana_url: trigger_url,
start: "2019-10-06T21:35:39Z", start: "2019-10-06T21:35:39Z",
end: "2019-10-07T21:35:39Z" end: "2019-10-07T21:35:39Z"
) )
expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
end end
context 'when the dashboard link is part of a paragraph' do it_behaves_like 'a metrics embed filter'
let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) }
let(:input) { %(<p>#{paragraph}</p>) }
it 'appends the charts placeholder after the enclosing paragraph' do
expect(unescape(doc.at_css('p').to_s)).to include(paragraph)
expect(doc.at_css('.js-render-metrics')).to be_present
end
end
context 'when grafana is not configured' do context 'when grafana is not configured' do
before do before do
......
...@@ -5,66 +5,31 @@ require 'spec_helper' ...@@ -5,66 +5,31 @@ require 'spec_helper'
describe Banzai::Filter::InlineMetricsFilter do describe Banzai::Filter::InlineMetricsFilter do
include FilterSpecHelper include FilterSpecHelper
let(:input) { %(<a href="#{url}">example</a>) }
let(:doc) { filter(input) }
context 'when the document has an external link' do
let(:url) { 'https://foo.com' }
it 'leaves regular non-metrics links unchanged' do
expect(doc.to_s).to eq(input)
end
end
context 'when the document has a metrics dashboard link' do
let(:params) { ['foo', 'bar', 12] } let(:params) { ['foo', 'bar', 12] }
let(:url) { urls.metrics_namespace_project_environment_url(*params) } let(:query_params) { {} }
it 'leaves the original link unchanged' do
expect(doc.at_css('a').to_s).to eq(input)
end
it 'appends a metrics charts placeholder with dashboard url after metrics links' do
node = doc.at_css('.js-render-metrics')
expect(node).to be_present
dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params, embedded: true) let(:trigger_url) { urls.metrics_namespace_project_environment_url(*params, query_params) }
expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url) let(:dashboard_url) { urls.metrics_dashboard_namespace_project_environment_url(*params, **query_params, embedded: true) }
end
context 'when the metrics dashboard link is part of a paragraph' do
let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) }
let(:input) { %(<p>#{paragraph}</p>) }
it 'appends the charts placeholder after the enclosing paragraph' do it_behaves_like 'a metrics embed filter'
expect(doc.at_css('p').to_s).to include(paragraph)
expect(doc.at_css('.js-render-metrics')).to be_present
end
end
context 'with dashboard params specified' do context 'with query params specified' do
let(:params) do let(:query_params) do
[
'foo',
'bar',
12,
{ {
embedded: true,
dashboard: 'config/prometheus/common_metrics.yml', dashboard: 'config/prometheus/common_metrics.yml',
group: 'System metrics (Kubernetes)', group: 'System metrics (Kubernetes)',
title: 'Core Usage (Pod Average)', title: 'Core Usage (Pod Average)',
y_label: 'Cores per Pod' y_label: 'Cores per Pod'
} }
]
end end
it 'appends a metrics charts placeholder with dashboard url after metrics links' do it_behaves_like 'a metrics embed filter'
node = doc.at_css('.js-render-metrics')
expect(node).to be_present
dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params)
expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
end
end end
it 'leaves links to other dashboards unchanged' do
url = urls.namespace_project_grafana_api_metrics_dashboard_url('foo', 'bar')
input = %(<a href="#{url}">example</a>)
expect(filter(input).to_s).to eq(input)
end end
end end
...@@ -18,33 +18,6 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do ...@@ -18,33 +18,6 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
end end
context 'with a metrics charts placeholder' do context 'with a metrics charts placeholder' do
shared_examples_for 'a supported metrics dashboard url' do
context 'no user is logged in' do
it 'redacts the placeholder' do
expect(doc.to_s).to be_empty
end
end
context 'the user does not have permission do see charts' do
let(:doc) { filter(input, current_user: build(:user)) }
it 'redacts the placeholder' do
expect(doc.to_s).to be_empty
end
end
context 'the user has requisite permissions' do
let(:user) { create(:user) }
let(:doc) { filter(input, current_user: user) }
it 'leaves the placeholder' do
project.add_maintainer(user)
expect(doc.to_s).to eq input
end
end
end
let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) } let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) }
it_behaves_like 'a supported metrics dashboard url' it_behaves_like 'a supported metrics dashboard url'
......
# frozen_string_literal: true
# Expects 2 attributes to be defined:
# trigger_url - Url expected to trigger the insertion of a placeholder.
# dashboard_url - Url expected to be present in the placeholder.
RSpec.shared_examples 'a metrics embed filter' do
let(:input) { %(<a href="#{url}">example</a>) }
let(:doc) { filter(input) }
context 'when the document has an external link' do
let(:url) { 'https://foo.com' }
it 'leaves regular non-metrics links unchanged' do
expect(doc.to_s).to eq(input)
end
end
context 'when the document contains an embeddable link' do
let(:url) { trigger_url }
it 'leaves the original link unchanged' do
expect(unescape(doc.at_css('a').to_s)).to eq(input)
end
it 'appends a metrics charts placeholder' do
node = doc.at_css('.js-render-metrics')
expect(node).to be_present
expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
end
context 'in a paragraph' do
let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) }
let(:input) { %(<p>#{paragraph}</p>) }
it 'appends a metrics charts placeholder after the enclosing paragraph' do
expect(unescape(doc.at_css('p').to_s)).to include(paragraph)
expect(doc.at_css('.js-render-metrics')).to be_present
end
end
end
# Nokogiri escapes the URLs, but we don't care about that
# distinction for the purposes of these filters
def unescape(html)
CGI.unescapeHTML(html)
end
end
# frozen_string_literal: true
RSpec.shared_examples 'a supported metrics dashboard url' do
context 'no user is logged in' do
it 'redacts the placeholder' do
expect(doc.to_s).to be_empty
end
end
context 'the user does not have permission do see charts' do
let(:doc) { filter(input, current_user: build(:user)) }
it 'redacts the placeholder' do
expect(doc.to_s).to be_empty
end
end
context 'the user has requisite permissions' do
let(:user) { create(:user) }
let(:doc) { filter(input, current_user: user) }
it 'leaves the placeholder' do
project.add_maintainer(user)
expect(doc.to_s).to eq(input)
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