Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAdam Buckland <adamjbuckland@gmail.com>2016-08-24 20:11:48 +0300
committerDouwe Maan <douwe@selenight.nl>2017-04-07 22:31:43 +0300
commitace833b31dfac64a8b44242ce7d91c60285bf983 (patch)
tree65868bc5cb28f228c09ad5dccb477e5769a39122 /lib
parent5d4449151fb576dc927ff1d0ff343fca4645159b (diff)
Add indication for closed or merged issuables in GFM
Example: for issues that are closed, the links will now show '[closed]' following the issue number. This is done as post-process after the markdown has been loaded from the cache as the status of the issue may change between the cache being populated and the content being displayed. In order to avoid N+1 queries problem when rendering notes ObjectRenderer populates the cache of referenced issuables for all notes at once, before the post processing phase. As a part of this change, the Banzai BaseParser#grouped_objects_for_nodes method has been refactored to return a Hash utilising the node itself as the key, since this was a common pattern of usage for this method.
Diffstat (limited to 'lib')
-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