summaryrefslogtreecommitdiff
path: root/lib/banzai
diff options
context:
space:
mode:
Diffstat (limited to 'lib/banzai')
-rw-r--r--lib/banzai/filter/issuable_state_filter.rb35
-rw-r--r--lib/banzai/filter/redactor_filter.rb2
-rw-r--r--lib/banzai/issuable_extractor.rb36
-rw-r--r--lib/banzai/object_renderer.rb46
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb1
-rw-r--r--lib/banzai/reference_parser/base_parser.rb20
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb10
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb8
-rw-r--r--lib/banzai/reference_parser/user_parser.rb6
9 files changed, 124 insertions, 40 deletions
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
new file mode 100644
index 00000000000..6b78aa795b4
--- /dev/null
+++ b/lib/banzai/filter/issuable_state_filter.rb
@@ -0,0 +1,35 @@
+module Banzai
+ module Filter
+ # HTML filter that appends state information to issuable links.
+ # Runs as a post-process filter as issuable state might change whilst
+ # Markdown is in the cache.
+ #
+ # This filter supports cross-project references.
+ class IssuableStateFilter < HTML::Pipeline::Filter
+ VISIBLE_STATES = %w(closed merged).freeze
+
+ def call
+ extractor = Banzai::IssuableExtractor.new(project, current_user)
+ issuables = extractor.extract([doc])
+
+ issuables.each do |node, issuable|
+ if VISIBLE_STATES.include?(issuable.state)
+ node.children.last.content += " [#{issuable.state}]"
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ def current_user
+ context[:current_user]
+ end
+
+ def project
+ context[:project]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index c59a80dd1c7..9f9882b3b40 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -7,7 +7,7 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
- Redactor.new(project, current_user).redact([doc])
+ Redactor.new(project, current_user).redact([doc]) unless context[:skip_redaction]
doc
end
diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb
new file mode 100644
index 00000000000..c5ce360e172
--- /dev/null
+++ b/lib/banzai/issuable_extractor.rb
@@ -0,0 +1,36 @@
+module Banzai
+ # Extract references to issuables from multiple documents
+
+ # This populates RequestStore cache used in Banzai::ReferenceParser::IssueParser
+ # and Banzai::ReferenceParser::MergeRequestParser
+ # Populating the cache should happen before processing documents one-by-one
+ # so we can avoid N+1 queries problem
+
+ class IssuableExtractor
+ QUERY = %q(
+ descendant-or-self::a[contains(concat(" ", @class, " "), " gfm ")]
+ [@data-reference-type="issue" or @data-reference-type="merge_request"]
+ ).freeze
+
+ attr_reader :project, :user
+
+ def initialize(project, user)
+ @project = project
+ @user = user
+ end
+
+ # Returns Hash in the form { node => issuable_instance }
+ def extract(documents)
+ nodes = documents.flat_map do |document|
+ document.xpath(QUERY)
+ end
+
+ issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user)
+ merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user)
+
+ issue_parser.issues_for_nodes(nodes).merge(
+ merge_request_parser.merge_requests_for_nodes(nodes)
+ )
+ end
+ end
+end
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 9f8eb0931b8..002a3341ccd 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -31,7 +31,8 @@ module Banzai
#
# Returns the same input objects.
def render(objects, attribute)
- documents = render_objects(objects, attribute)
+ documents = render_documents(objects, attribute)
+ documents = post_process_documents(documents, objects, attribute)
redacted = redact_documents(documents)
objects.each_with_index do |object, index|
@@ -41,9 +42,24 @@ module Banzai
end
end
- # Renders the attribute of every given object.
- def render_objects(objects, attribute)
- render_attributes(objects, attribute)
+ private
+
+ def render_documents(objects, attribute)
+ pipeline = HTML::Pipeline.new([])
+
+ objects.map do |object|
+ pipeline.to_document(Banzai.render_field(object, attribute))
+ end
+ end
+
+ def post_process_documents(documents, objects, attribute)
+ # Called here to populate cache, refer to IssuableExtractor docs
+ IssuableExtractor.new(project, user).extract(documents)
+
+ documents.zip(objects).map do |document, object|
+ context = context_for(object, attribute)
+ Banzai::Pipeline[:post_process].to_document(document, context)
+ end
end
# Redacts the list of documents.
@@ -57,25 +73,15 @@ module Banzai
# Returns a Banzai context for the given object and attribute.
def context_for(object, attribute)
- context = base_context.dup
- context = context.merge(object.banzai_render_context(attribute))
- context
- end
-
- # Renders the attributes of a set of objects.
- #
- # Returns an Array of `Nokogiri::HTML::Document`.
- def render_attributes(objects, attribute)
- objects.map do |object|
- string = Banzai.render_field(object, attribute)
- context = context_for(object, attribute)
-
- Banzai::Pipeline[:relative_link].to_document(string, context)
- end
+ base_context.merge(object.banzai_render_context(attribute))
end
def base_context
- @base_context ||= @redaction_context.merge(current_user: user, project: project)
+ @base_context ||= @redaction_context.merge(
+ current_user: user,
+ project: project,
+ skip_redaction: true
+ )
end
end
end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index ecff094b1e5..131ac3b0eec 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -4,6 +4,7 @@ module Banzai
def self.filters
FilterArray[
Filter::RelativeLinkFilter,
+ Filter::IssuableStateFilter,
Filter::RedactorFilter
]
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 52fdb9a2140..dabf71d6aeb 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -62,8 +62,7 @@ module Banzai
nodes.select do |node|
if node.has_attribute?(project_attr)
- node_id = node.attr(project_attr).to_i
- can_read_reference?(user, projects[node_id])
+ can_read_reference?(user, projects[node])
else
true
end
@@ -112,12 +111,12 @@ module Banzai
per_project
end
- # Returns a Hash containing objects for an attribute grouped per their
- # IDs.
+ # Returns a Hash containing objects for an attribute grouped per the
+ # nodes that reference them.
#
# The returned Hash uses the following format:
#
- # { id value => row }
+ # { node => row }
#
# nodes - An Array of HTML nodes to process.
#
@@ -132,9 +131,14 @@ module Banzai
return {} if nodes.empty?
ids = unique_attribute_values(nodes, attribute)
- rows = collection_objects_for_ids(collection, ids)
+ collection_objects = collection_objects_for_ids(collection, ids)
+ objects_by_id = collection_objects.index_by(&:id)
- rows.index_by(&:id)
+ nodes.each_with_object({}) do |node, hash|
+ if node.has_attribute?(attribute)
+ hash[node] = objects_by_id[node.attr(attribute).to_i]
+ end
+ end
end
# Returns an Array containing all unique values of an attribute of the
@@ -201,7 +205,7 @@ module Banzai
#
# The returned Hash uses the following format:
#
- # { project ID => project }
+ # { node => project }
#
def projects_for_nodes(nodes)
@projects_for_nodes ||=
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index 6c20dec5734..e02b360924a 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -13,14 +13,14 @@ module Banzai
issues_readable_by_user(issues.values, user).to_set
nodes.select do |node|
- readable_issues.include?(issue_for_node(issues, node))
+ readable_issues.include?(issues[node])
end
end
def referenced_by(nodes)
issues = issues_for_nodes(nodes)
- nodes.map { |node| issue_for_node(issues, node) }.uniq
+ nodes.map { |node| issues[node] }.compact.uniq
end
def issues_for_nodes(nodes)
@@ -44,12 +44,6 @@ module Banzai
self.class.data_attribute
)
end
-
- private
-
- def issue_for_node(issues, node)
- issues[node.attr(self.class.data_attribute).to_i]
- end
end
end
end
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index 40451947e6c..7d7dcce017e 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -3,6 +3,14 @@ module Banzai
class MergeRequestParser < BaseParser
self.reference_type = :merge_request
+ def merge_requests_for_nodes(nodes)
+ @merge_requests_for_nodes ||= grouped_objects_for_nodes(
+ nodes,
+ MergeRequest.all,
+ self.class.data_attribute
+ )
+ end
+
def references_relation
MergeRequest.includes(:author, :assignee, :target_project)
end
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index 7adaffa19c1..09b66cbd8fb 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -49,7 +49,7 @@ module Banzai
# Check if project belongs to a group which
# user can read.
def can_read_group_reference?(node, user, groups)
- node_group = groups[node.attr('data-group').to_i]
+ node_group = groups[node]
node_group && can?(user, :read_group, node_group)
end
@@ -74,8 +74,8 @@ module Banzai
if project && project_id && project.id == project_id.to_i
true
elsif project_id && user_id
- project = projects[project_id.to_i]
- user = users[user_id.to_i]
+ project = projects[node]
+ user = users[node]
project && user ? project.team.member?(user) : false
else