summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/banzai/filter/inline_grafana_metrics_filter.rb78
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb89
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/gitlab/metrics/dashboard/finder.rb3
-rw-r--r--lib/gitlab/metrics/dashboard/service_selector.rb5
-rw-r--r--lib/gitlab/metrics/dashboard/url.rb46
-rw-r--r--lib/gitlab/workhorse.rb1
7 files changed, 185 insertions, 38 deletions
diff --git a/lib/banzai/filter/inline_grafana_metrics_filter.rb b/lib/banzai/filter/inline_grafana_metrics_filter.rb
new file mode 100644
index 00000000000..34d93a59f48
--- /dev/null
+++ b/lib/banzai/filter/inline_grafana_metrics_filter.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that inserts a placeholder element for each
+ # reference to a grafana dashboard.
+ class InlineGrafanaMetricsFilter < Banzai::Filter::InlineEmbedsFilter
+ # Placeholder element for the frontend to use as an
+ # injection point for charts.
+ def create_element(params)
+ begin_loading_dashboard(params[:url])
+
+ doc.document.create_element(
+ 'div',
+ class: 'js-render-metrics',
+ 'data-dashboard-url': metrics_dashboard_url(params)
+ )
+ end
+
+ def embed_params(node)
+ return unless Feature.enabled?(:gfm_grafana_integration)
+
+ query_params = Gitlab::Metrics::Dashboard::Url.parse_query(node['href'])
+ return unless [:panelId, :from, :to].all? do |param|
+ query_params.include?(param)
+ end
+
+ { url: node['href'], start: query_params[:from], end: query_params[:to] }
+ end
+
+ # Selects any links with an href contains the configured
+ # grafana domain for the project
+ def xpath_search
+ return unless grafana_url.present?
+
+ %(descendant-or-self::a[starts-with(@href, '#{grafana_url}')])
+ end
+
+ private
+
+ def project
+ context[:project]
+ end
+
+ def grafana_url
+ project&.grafana_integration&.grafana_url
+ end
+
+ def metrics_dashboard_url(params)
+ Gitlab::Routing.url_helpers.project_grafana_api_metrics_dashboard_url(
+ project,
+ embedded: true,
+ grafana_url: params[:url],
+ start: format_time(params[:start]),
+ end: format_time(params[:end])
+ )
+ end
+
+ # Formats a timestamp from Grafana for compatibility with
+ # parsing in JS via `new Date(timestamp)`
+ #
+ # @param time [String] Represents miliseconds since epoch
+ def format_time(time)
+ Time.at(time.to_i / 1000).utc.strftime('%FT%TZ')
+ end
+
+ # Fetches a dashboard and caches the result for the
+ # FE to fetch quickly while rendering charts
+ def begin_loading_dashboard(url)
+ ::Gitlab::Metrics::Dashboard::Finder.find(
+ project,
+ embedded: true,
+ grafana_url: url
+ )
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
index 4d8a5028898..e84ba83e03e 100644
--- a/lib/banzai/filter/inline_metrics_redactor_filter.rb
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -8,14 +8,17 @@ module Banzai
include Gitlab::Utils::StrongMemoize
METRICS_CSS_CLASS = '.js-render-metrics'
+ URL = Gitlab::Metrics::Dashboard::Url
+
+ Embed = Struct.new(:project_path, :permission)
# Finds all embeds based on the css class the FE
# uses to identify the embedded content, removing
# only unnecessary nodes.
def call
nodes.each do |node|
- path = paths_by_node[node]
- user_has_access = user_access_by_path[path]
+ embed = embeds_by_node[node]
+ user_has_access = user_access_by_embed[embed]
node.remove unless user_has_access
end
@@ -30,40 +33,69 @@ module Banzai
end
# Returns all nodes which the FE will identify as
- # a metrics dashboard placeholder element
+ # a metrics embed placeholder element
#
# @return [Nokogiri::XML::NodeSet]
def nodes
@nodes ||= doc.css(METRICS_CSS_CLASS)
end
- # Maps a node to the full path of a project.
+ # Maps a node to key properties of an embed.
# Memoized so we only need to run the regex to get
# the project full path from the url once per node.
#
- # @return [Hash<Nokogiri::XML::Node, String>]
- def paths_by_node
- strong_memoize(:paths_by_node) do
- nodes.each_with_object({}) do |node, paths|
- paths[node] = path_for_node(node)
+ # @return [Hash<Nokogiri::XML::Node, Embed>]
+ def embeds_by_node
+ strong_memoize(:embeds_by_node) do
+ nodes.each_with_object({}) do |node, embeds|
+ embed = Embed.new
+ url = node.attribute('data-dashboard-url').to_s
+
+ set_path_and_permission(embed, url, URL.regex, :read_environment)
+ set_path_and_permission(embed, url, URL.grafana_regex, :read_project) unless embed.permission
+
+ embeds[node] = embed if embed.permission
end
end
end
- # Gets a project's full_path from the dashboard url
- # in the placeholder node. The FE will use the attr
- # `data-dashboard-url`, so we want to check against that
- # attribute directly in case a user has manually
- # created a metrics element (rather than supporting
- # an alternate attr in InlineMetricsFilter).
+ # Attempts to determine the path and permission attributes
+ # of a url based on expected dashboard url formats and
+ # sets the attributes on an Embed object
#
- # @return [String]
- def path_for_node(node)
- url = node.attribute('data-dashboard-url').to_s
-
- Gitlab::Metrics::Dashboard::Url.regex.match(url) do |m|
+ # @param embed [Embed]
+ # @param url [String]
+ # @param regex [RegExp]
+ # @param permission [Symbol]
+ def set_path_and_permission(embed, url, regex, permission)
+ return unless path = regex.match(url) do |m|
"#{$~[:namespace]}/#{$~[:project]}"
end
+
+ embed.project_path = path
+ embed.permission = permission
+ end
+
+ # Returns a mapping representing whether the current user
+ # has permission to view the embed for the project.
+ # Determined in a batch
+ #
+ # @return [Hash<Embed, Boolean>]
+ def user_access_by_embed
+ strong_memoize(:user_access_by_embed) do
+ unique_embeds.each_with_object({}) do |embed, access|
+ project = projects_by_path[embed.project_path]
+
+ access[embed] = Ability.allowed?(user, embed.permission, project)
+ end
+ end
+ end
+
+ # Returns a unique list of embeds
+ #
+ # @return [Array<Embed>]
+ def unique_embeds
+ embeds_by_node.values.uniq
end
# Maps a project's full path to a Project object.
@@ -74,22 +106,17 @@ module Banzai
def projects_by_path
strong_memoize(:projects_by_path) do
Project.eager_load(:route, namespace: [:route])
- .where_full_path_in(paths_by_node.values.uniq)
+ .where_full_path_in(unique_project_paths)
.index_by(&:full_path)
end
end
- # Returns a mapping representing whether the current user
- # has permission to view the metrics for the project.
- # Determined in a batch
+ # Returns a list of the full_paths of every project which
+ # has an embed in the doc
#
- # @return [Hash<Project, Boolean>]
- def user_access_by_path
- strong_memoize(:user_access_by_path) do
- projects_by_path.each_with_object({}) do |(path, project), access|
- access[path] = Ability.allowed?(user, :read_environment, project)
- end
- end
+ # @return [Array<String>]
+ def unique_project_paths
+ embeds_by_node.values.map(&:project_path).uniq
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 08e27257fdf..f6c12cdb53b 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -30,6 +30,7 @@ module Banzai
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
Filter::InlineMetricsFilter,
+ Filter::InlineGrafanaMetricsFilter,
Filter::TableOfContentsFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb
index 297f109ff81..268112f33a9 100644
--- a/lib/gitlab/metrics/dashboard/finder.rb
+++ b/lib/gitlab/metrics/dashboard/finder.rb
@@ -12,6 +12,7 @@ module Gitlab
# @param project [Project]
# @param user [User]
# @param environment [Environment]
+ # @param options [Hash<Symbol,Any>]
# @param options - embedded [Boolean] Determines whether the
# dashboard is to be rendered as part of an
# issue or location other than the primary
@@ -31,6 +32,8 @@ module Gitlab
# @param options - cluster [Cluster]
# @param options - cluster_type [Symbol] The level of
# cluster, one of [:admin, :project, :group]
+ # @param options - grafana_url [String] URL pointing
+ # to a grafana dashboard panel
# @return [Hash]
def find(project, user, options = {})
service_for(options)
diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb
index 10b686fbb81..aee7f6685ad 100644
--- a/lib/gitlab/metrics/dashboard/service_selector.rb
+++ b/lib/gitlab/metrics/dashboard/service_selector.rb
@@ -18,6 +18,7 @@ module Gitlab
# @return [Gitlab::Metrics::Dashboard::Services::BaseService]
def call(params)
return SERVICES::CustomMetricEmbedService if custom_metric_embed?(params)
+ return SERVICES::GrafanaMetricEmbedService if grafana_metric_embed?(params)
return SERVICES::DynamicEmbedService if dynamic_embed?(params)
return SERVICES::DefaultEmbedService if params[:embedded]
return SERVICES::SystemDashboardService if system_dashboard?(params[:dashboard_path])
@@ -40,6 +41,10 @@ module Gitlab
SERVICES::CustomMetricEmbedService.valid_params?(params)
end
+ def grafana_metric_embed?(params)
+ SERVICES::GrafanaMetricEmbedService.valid_params?(params)
+ end
+
def dynamic_embed?(params)
SERVICES::DynamicEmbedService.valid_params?(params)
end
diff --git a/lib/gitlab/metrics/dashboard/url.rb b/lib/gitlab/metrics/dashboard/url.rb
index 94f8b2e02b1..712f769bbeb 100644
--- a/lib/gitlab/metrics/dashboard/url.rb
+++ b/lib/gitlab/metrics/dashboard/url.rb
@@ -14,17 +14,31 @@ module Gitlab
def regex
%r{
(?<url>
- #{Regexp.escape(Gitlab.config.gitlab.url)}
- \/#{Project.reference_pattern}
+ #{gitlab_pattern}
+ #{project_pattern}
(?:\/\-)?
\/environments
\/(?<environment>\d+)
\/metrics
- (?<query>
- \?[a-zA-Z0-9%.()+_=-]+
- (&[a-zA-Z0-9%.()+_=-]+)*
- )?
- (?<anchor>\#[a-z0-9_-]+)?
+ #{query_pattern}
+ #{anchor_pattern}
+ )
+ }x
+ end
+
+ # Matches dashboard urls for a Grafana embed.
+ #
+ # EX - https://<host>/<namespace>/<project>/grafana/metrics_dashboard
+ def grafana_regex
+ %r{
+ (?<url>
+ #{gitlab_pattern}
+ #{project_pattern}
+ (?:\/\-)?
+ \/grafana
+ \/metrics_dashboard
+ #{query_pattern}
+ #{anchor_pattern}
)
}x
end
@@ -45,6 +59,24 @@ module Gitlab
def build_dashboard_url(*args)
Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_environment_url(*args)
end
+
+ private
+
+ def gitlab_pattern
+ Regexp.escape(Gitlab.config.gitlab.url)
+ end
+
+ def project_pattern
+ "\/#{Project.reference_pattern}"
+ end
+
+ def query_pattern
+ '(?<query>\?[a-zA-Z0-9%.()+_=-]+(&[a-zA-Z0-9%.()+_=-]+)*)?'
+ end
+
+ def anchor_pattern
+ '(?<anchor>\#[a-z0-9_-]+)?'
+ end
end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index db67e4fd479..713ca31bbc5 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -14,6 +14,7 @@ module Gitlab
NOTIFICATION_CHANNEL = 'workhorse:notifications'
ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze
DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type'
+ ARCHIVE_FORMATS = %w(zip tar.gz tar.bz2 tar).freeze
include JwtAuthenticatable