diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-31 15:06:41 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-31 15:06:41 +0000 |
commit | 083d64c6468a070ae7b0b406ead8d87da27d1d22 (patch) | |
tree | ba92a9b5b6418f805ede9ef05b371d82d2c1d27d /lib/banzai | |
parent | 0be510a49f6e4f8e27b19b707fd1dac61571f78f (diff) | |
download | gitlab-ce-083d64c6468a070ae7b0b406ead8d87da27d1d22.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'lib/banzai')
-rw-r--r-- | lib/banzai/filter/inline_grafana_metrics_filter.rb | 78 | ||||
-rw-r--r-- | lib/banzai/filter/inline_metrics_redactor_filter.rb | 89 | ||||
-rw-r--r-- | lib/banzai/pipeline/gfm_pipeline.rb | 1 |
3 files changed, 137 insertions, 31 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, |