gitlab-org--gitlab-foss/lib/banzai/reference_parser/user_parser.rb
Yorick Peterse daad7144ec
Support Markdown rendering using multiple projects
This refactors the Markdown pipeline so it supports the rendering of
multiple documents that may belong to different projects. An example of
where this happens is when displaying the event feed of a group. In this
case we retrieve events for all projects in the group. Previously we
would group events per project and render these chunks separately, but
this would result in many SQL queries being executed. By extending the
Markdown pipeline to support this out of the box we can drastically
reduce the number of SQL queries.

To achieve this we introduce a new object to the pipeline:
Banzai::RenderContext. This object simply wraps two other objects: an
optional Project instance, and an optional User instance. On its own
this wouldn't be very helpful, but a RenderContext can also be used to
associate HTML documents with specific Project instances. This work is
done in Banzai::ObjectRenderer and allows us to reuse as many queries
(and results) as possible.
2018-04-11 14:10:19 +02:00

113 lines
3.2 KiB
Ruby

module Banzai
module ReferenceParser
class UserParser < BaseParser
self.reference_type = :user
def referenced_by(nodes)
group_ids = []
user_ids = []
project_ids = []
nodes.each do |node|
if node.has_attribute?('data-group')
group_ids << node.attr('data-group').to_i
elsif node.has_attribute?(self.class.data_attribute)
user_ids << node.attr(self.class.data_attribute).to_i
elsif node.has_attribute?('data-project')
project_ids << node.attr('data-project').to_i
end
end
find_users_for_groups(group_ids) | find_users(user_ids) |
find_users_for_projects(project_ids)
end
def nodes_visible_to_user(user, nodes)
group_attr = 'data-group'
groups = lazy { grouped_objects_for_nodes(nodes, Group, group_attr) }
visible = []
remaining = []
nodes.each do |node|
if node.has_attribute?(group_attr)
next unless can_read_group_reference?(node, user, groups)
visible << node
elsif can_read_project_reference?(node)
visible << node
else
remaining << node
end
end
# If project does not belong to a group
# and does not have the same project id as the current project
# base class will check if user can read the project that contains
# the user reference.
visible + super(current_user, remaining)
end
# Check if project belongs to a group which
# user can read.
def can_read_group_reference?(node, user, groups)
node_group = groups[node]
node_group && can?(user, :read_group, node_group)
end
def can_read_project_reference?(node)
node_id = node.attr('data-project').to_i
project_for_node(node)&.id == node_id
end
def nodes_user_can_reference(current_user, nodes)
project_attr = 'data-project'
author_attr = 'data-author'
projects = lazy { projects_for_nodes(nodes) }
users = lazy { grouped_objects_for_nodes(nodes, User, author_attr) }
nodes.select do |node|
project_id = node.attr(project_attr)
user_id = node.attr(author_attr)
project = project_for_node(node)
if project && project_id && project.id == project_id.to_i
true
elsif project_id && user_id
project = projects[node]
user = users[node]
project && user ? project.team.member?(user) : false
else
true
end
end
end
def find_users(ids)
return [] if ids.empty?
collection_objects_for_ids(User, ids)
end
def find_users_for_groups(ids)
return [] if ids.empty?
User.joins(:group_members).where(members: { source_id: ids }).to_a
end
def find_users_for_projects(ids)
return [] if ids.empty?
collection_objects_for_ids(Project, ids)
.flat_map { |p| p.team.members.to_a }
end
def can_read_reference?(user, ref_project, node)
can?(user, :read_project, ref_project)
end
end
end
end