Commit daad7144 authored by Yorick Peterse's avatar Yorick Peterse

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.
parent 23fb465c
...@@ -41,7 +41,7 @@ module NotesActions ...@@ -41,7 +41,7 @@ module NotesActions
@note = Notes::CreateService.new(note_project, current_user, create_params).execute @note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note) if @note.is_a?(Note)
Notes::RenderService.new(current_user).execute([@note], @project) Notes::RenderService.new(current_user).execute([@note])
end end
respond_to do |format| respond_to do |format|
...@@ -56,7 +56,7 @@ module NotesActions ...@@ -56,7 +56,7 @@ module NotesActions
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note) if @note.is_a?(Note)
Notes::RenderService.new(current_user).execute([@note], @project) Notes::RenderService.new(current_user).execute([@note])
end end
respond_to do |format| respond_to do |format|
......
...@@ -4,7 +4,7 @@ module RendersNotes ...@@ -4,7 +4,7 @@ module RendersNotes
preload_noteable_for_regular_notes(notes) preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project) preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes) preload_first_time_contribution_for_authors(noteable, notes)
Notes::RenderService.new(current_user).execute(notes, @project) Notes::RenderService.new(current_user).execute(notes)
notes notes
end end
......
...@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController ...@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController
.new(@projects, offset: params[:offset].to_i, filter: event_filter) .new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a .to_a
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?) Events::RenderService
.new(current_user)
.execute(@events, atom_request: request.format.atom?)
end end
def user_actions def user_actions
......
...@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
private private
def render_json_with_notes_serializer def render_json_with_notes_serializer
Notes::RenderService.new(current_user).execute([note], project) Notes::RenderService.new(current_user).execute([note])
render json: note_serializer.represent(note) render json: note_serializer.represent(note)
end end
......
...@@ -256,7 +256,7 @@ module MarkupHelper ...@@ -256,7 +256,7 @@ module MarkupHelper
return '' unless html.present? return '' unless html.present?
context.merge!( context.merge!(
current_user: (current_user if defined?(current_user)), current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter # RelativeLinkFilter
commit: @commit, commit: @commit,
......
module Events module Events
class RenderService < BaseRenderer class RenderService < BaseRenderer
def execute(events, atom_request: false) def execute(events, atom_request: false)
events.map(&:note).compact.group_by(&:project).each do |project, notes| notes = events.map(&:note).compact
render_notes(notes, project, atom_request)
end render_notes(notes, atom_request)
end end
private private
def render_notes(notes, project, atom_request) def render_notes(notes, atom_request)
Notes::RenderService.new(current_user).execute(notes, project, render_options(atom_request)) Notes::RenderService
.new(current_user)
.execute(notes, render_options(atom_request))
end end
def render_options(atom_request) def render_options(atom_request)
......
...@@ -3,19 +3,18 @@ module Notes ...@@ -3,19 +3,18 @@ module Notes
# Renders a collection of Note instances. # Renders a collection of Note instances.
# #
# notes - The notes to render. # notes - The notes to render.
# project - The project to use for redacting. #
# user - The user viewing the notes.
# Possible options: # Possible options:
#
# requested_path - The request path. # requested_path - The request path.
# project_wiki - The project's wiki. # project_wiki - The project's wiki.
# ref - The current Git reference. # ref - The current Git reference.
# only_path - flag to turn relative paths into absolute ones. # only_path - flag to turn relative paths into absolute ones.
# xhtml - flag to save the html in XHTML # xhtml - flag to save the html in XHTML
def execute(notes, project, **opts) def execute(notes, options = {})
renderer = Banzai::ObjectRenderer.new(project, current_user, **opts) Banzai::ObjectRenderer
.new(user: current_user, redaction_context: options)
renderer.render(notes, :note) .render(notes, :note)
end end
end end
end end
---
title: Support Markdown rendering using multiple projects
merge_request:
author:
type: performance
...@@ -3,7 +3,7 @@ module Banzai ...@@ -3,7 +3,7 @@ module Banzai
ATTRIBUTES = [:description, :title].freeze ATTRIBUTES = [:description, :title].freeze
def self.render(commits, project, user = nil) def self.render(commits, project, user = nil)
obj_renderer = ObjectRenderer.new(project, user) obj_renderer = ObjectRenderer.new(user: user, default_project: project)
ATTRIBUTES.each { |attr| obj_renderer.render(commits, attr) } ATTRIBUTES.each { |attr| obj_renderer.render(commits, attr) }
end end
......
...@@ -11,7 +11,8 @@ module Banzai ...@@ -11,7 +11,8 @@ module Banzai
def call def call
return doc unless context[:issuable_state_filter_enabled] return doc unless context[:issuable_state_filter_enabled]
extractor = Banzai::IssuableExtractor.new(project, current_user) context = RenderContext.new(project, current_user)
extractor = Banzai::IssuableExtractor.new(context)
issuables = extractor.extract([doc]) issuables = extractor.extract([doc])
issuables.each do |node, issuable| issuables.each do |node, issuable|
......
...@@ -7,7 +7,11 @@ module Banzai ...@@ -7,7 +7,11 @@ module Banzai
# #
class RedactorFilter < HTML::Pipeline::Filter class RedactorFilter < HTML::Pipeline::Filter
def call def call
Redactor.new(project, current_user).redact([doc]) unless context[:skip_redaction] unless context[:skip_redaction]
context = RenderContext.new(project, current_user)
Redactor.new(context).redact([doc])
end
doc doc
end end
......
...@@ -12,11 +12,11 @@ module Banzai ...@@ -12,11 +12,11 @@ module Banzai
[@data-reference-type="issue" or @data-reference-type="merge_request"] [@data-reference-type="issue" or @data-reference-type="merge_request"]
).freeze ).freeze
attr_reader :project, :user attr_reader :context
def initialize(project, user) # context - An instance of Banzai::RenderContext.
@project = project def initialize(context)
@user = user @context = context
end end
# Returns Hash in the form { node => issuable_instance } # Returns Hash in the form { node => issuable_instance }
...@@ -25,8 +25,10 @@ module Banzai ...@@ -25,8 +25,10 @@ module Banzai
document.xpath(QUERY) document.xpath(QUERY)
end end
issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user) issue_parser = Banzai::ReferenceParser::IssueParser.new(context)
merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user)
merge_request_parser =
Banzai::ReferenceParser::MergeRequestParser.new(context)
issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge( issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge(
merge_request_parser.records_for_nodes(nodes) merge_request_parser.records_for_nodes(nodes)
......
...@@ -13,14 +13,13 @@ module Banzai ...@@ -13,14 +13,13 @@ module Banzai
# As an example, rendering the attribute `note` would place the unredacted # As an example, rendering the attribute `note` would place the unredacted
# HTML into `note_html` and the redacted HTML into `redacted_note_html`. # HTML into `note_html` and the redacted HTML into `redacted_note_html`.
class ObjectRenderer class ObjectRenderer
attr_reader :project, :user attr_reader :context
# project - A Project to use for redacting Markdown. # default_project - A default Project to use for redacting Markdown.
# user - The user viewing the Markdown/HTML documents, if any. # user - The user viewing the Markdown/HTML documents, if any.
# redaction_context - A Hash containing extra attributes to use during redaction # redaction_context - A Hash containing extra attributes to use during redaction
def initialize(project, user = nil, redaction_context = {}) def initialize(default_project: nil, user: nil, redaction_context: {})
@project = project @context = RenderContext.new(default_project, user)
@user = user
@redaction_context = base_context.merge(redaction_context) @redaction_context = base_context.merge(redaction_context)
end end
...@@ -48,17 +47,21 @@ module Banzai ...@@ -48,17 +47,21 @@ module Banzai
pipeline = HTML::Pipeline.new([]) pipeline = HTML::Pipeline.new([])
objects.map do |object| objects.map do |object|
pipeline.to_document(Banzai.render_field(object, attribute)) document = pipeline.to_document(Banzai.render_field(object, attribute))
context.associate_document(document, object)
document
end end
end end
def post_process_documents(documents, objects, attribute) def post_process_documents(documents, objects, attribute)
# Called here to populate cache, refer to IssuableExtractor docs # Called here to populate cache, refer to IssuableExtractor docs
IssuableExtractor.new(project, user).extract(documents) IssuableExtractor.new(context).extract(documents)
documents.zip(objects).map do |document, object| documents.zip(objects).map do |document, object|
context = context_for(object, attribute) pipeline_context = context_for(document, object, attribute)
Banzai::Pipeline[:post_process].to_document(document, context) Banzai::Pipeline[:post_process].to_document(document, pipeline_context)
end end
end end
...@@ -66,20 +69,21 @@ module Banzai ...@@ -66,20 +69,21 @@ module Banzai
# #
# Returns an Array containing the redacted documents. # Returns an Array containing the redacted documents.
def redact_documents(documents) def redact_documents(documents)
redactor = Redactor.new(project, user) redactor = Redactor.new(context)
redactor.redact(documents) redactor.redact(documents)
end end
# Returns a Banzai context for the given object and attribute. # Returns a Banzai context for the given object and attribute.
def context_for(object, attribute) def context_for(document, object, attribute)
@redaction_context.merge(object.banzai_render_context(attribute)) @redaction_context.merge(object.banzai_render_context(attribute)).merge(
project: context.project_for_node(document)
)
end end
def base_context def base_context
{ {
current_user: user, current_user: context.current_user,
project: project,
skip_redaction: true skip_redaction: true
} }
end end
......
...@@ -2,13 +2,15 @@ module Banzai ...@@ -2,13 +2,15 @@ module Banzai
# Class for removing Markdown references a certain user is not allowed to # Class for removing Markdown references a certain user is not allowed to
# view. # view.
class Redactor class Redactor
attr_reader :user, :project attr_reader :context
# project - A Project to use for redacting links. # context - An instance of `Banzai::RenderContext`.
# user - The currently logged in user (if any). def initialize(context)
def initialize(project, user = nil) @context = context
@project = project end
@user = user
def user
context.current_user
end end
# Redacts the references in the given Array of documents. # Redacts the references in the given Array of documents.
...@@ -70,11 +72,11 @@ module Banzai ...@@ -70,11 +72,11 @@ module Banzai
end end
def redact_cross_project_references(documents) def redact_cross_project_references(documents)
extractor = Banzai::IssuableExtractor.new(project, user) extractor = Banzai::IssuableExtractor.new(context)
issuables = extractor.extract(documents) issuables = extractor.extract(documents)
issuables.each do |node, issuable| issuables.each do |node, issuable|
next if issuable.project == project next if issuable.project == context.project_for_node(node)
node['class'] = node['class'].gsub('has-tooltip', '') node['class'] = node['class'].gsub('has-tooltip', '')
node['title'] = nil node['title'] = nil
...@@ -95,7 +97,7 @@ module Banzai ...@@ -95,7 +97,7 @@ module Banzai
end end
per_type.each do |type, nodes| per_type.each do |type, nodes|
parser = Banzai::ReferenceParser[type].new(project, user) parser = Banzai::ReferenceParser[type].new(context)
visible.merge(parser.nodes_visible_to_user(user, nodes)) visible.merge(parser.nodes_visible_to_user(user, nodes))
end end
......
...@@ -10,8 +10,8 @@ module Banzai ...@@ -10,8 +10,8 @@ module Banzai
end end
def references(type, project, current_user = nil) def references(type, project, current_user = nil)
processor = Banzai::ReferenceParser[type] context = RenderContext.new(project, current_user)
.new(project, current_user) processor = Banzai::ReferenceParser[type].new(context)
processor.process(html_documents) processor.process(html_documents)
end end
......
...@@ -45,9 +45,13 @@ module Banzai ...@@ -45,9 +45,13 @@ module Banzai
@data_attribute ||= "data-#{reference_type.to_s.dasherize}" @data_attribute ||= "data-#{reference_type.to_s.dasherize}"
end end
def initialize(project = nil, current_user = nil) # context - An instance of `Banzai::RenderContext`.
@project = project def initialize(context)
@current_user = current_user @context = context
end
def project_for_node(node)
context.project_for_node(node)
end end
# Returns all the nodes containing references that the user can refer to. # Returns all the nodes containing references that the user can refer to.
...@@ -224,7 +228,11 @@ module Banzai ...@@ -224,7 +228,11 @@ module Banzai
private private
attr_reader :current_user, :project attr_reader :context
def current_user
context.current_user
end
# When a feature is disabled or visible only for # When a feature is disabled or visible only for
# team members we should not allow team members # team members we should not allow team members
......
...@@ -5,15 +5,10 @@ module Banzai ...@@ -5,15 +5,10 @@ module Banzai
def nodes_visible_to_user(user, nodes) def nodes_visible_to_user(user, nodes)
issues = records_for_nodes(nodes) issues = records_for_nodes(nodes)
issues_to_check = issues.values issues_to_check, cross_project_issues = partition_issues(issues, user)
unless can?(user, :read_cross_project) readable_issues =
issues_to_check, cross_project_issues = issues_to_check.partition do |issue| Ability.issues_readable_by_user(issues_to_check, user).to_set
issue.project == project
end
end
readable_issues = Ability.issues_readable_by_user(issues_to_check, user).to_set
nodes.select do |node| nodes.select do |node|
issue_in_node = issues[node] issue_in_node = issues[node]
...@@ -25,7 +20,7 @@ module Banzai ...@@ -25,7 +20,7 @@ module Banzai
# but not the issue. # but not the issue.
if readable_issues.include?(issue_in_node) if readable_issues.include?(issue_in_node)
true true
elsif cross_project_issues&.include?(issue_in_node) elsif cross_project_issues.include?(issue_in_node)
can_read_reference?(user, issue_in_node) can_read_reference?(user, issue_in_node)
else else
false false
...@@ -33,6 +28,32 @@ module Banzai ...@@ -33,6 +28,32 @@ module Banzai
end end
end end
# issues - A Hash mapping HTML nodes to their corresponding Issue
# instances.
# user - The current User.
def partition_issues(issues, user)
return [issues.values, []] if can?(user, :read_cross_project)
issues_to_check = []
cross_project_issues = []
# We manually partition the data since our input is a Hash and our
# output has to be an Array of issues; not an Array of (node, issue)
# pairs.
issues.each do |node, issue|
target =
if issue.project == project_for_node(node)
issues_to_check
else
cross_project_issues
end
target << issue
end
[issues_to_check, cross_project_issues]
end
def records_for_nodes(nodes) def records_for_nodes(nodes)
@issues_for_nodes ||= grouped_objects_for_nodes( @issues_for_nodes ||= grouped_objects_for_nodes(
nodes, nodes,
......
...@@ -58,7 +58,7 @@ module Banzai ...@@ -58,7 +58,7 @@ module Banzai
def can_read_project_reference?(node) def can_read_project_reference?(node)
node_id = node.attr('data-project').to_i node_id = node.attr('data-project').to_i
project && project.id == node_id project_for_node(node)&.id == node_id
end end
def nodes_user_can_reference(current_user, nodes) def nodes_user_can_reference(current_user, nodes)
...@@ -71,6 +71,7 @@ module Banzai ...@@ -71,6 +71,7 @@ module Banzai
nodes.select do |node| nodes.select do |node|
project_id = node.attr(project_attr) project_id = node.attr(project_attr)
user_id = node.attr(author_attr) user_id = node.attr(author_attr)
project = project_for_node(node)
if project && project_id && project.id == project_id.to_i if project && project_id && project.id == project_id.to_i
true true
......
# frozen_string_literal: true
module Banzai
# Object storing the current user, project, and other details used when
# parsing Markdown references.
class RenderContext
attr_reader :current_user
# default_project - The default project to use for all documents, if any.
# current_user - The user viewing the document, if any.
def initialize(default_project = nil, current_user = nil)
@current_user = current_user
@projects = Hash.new(default_project)
end
# Associates an HTML document with a Project.
#
# document - The HTML document to map to a Project.
# object - The object that produced the HTML document.
def associate_document(document, object)
# XML nodes respond to "document" but will return a Document instance,
# even when they belong to a DocumentFragment.
document = document.document if document.fragment?
@projects[document] = object.project if object.respond_to?(:project)
end
def project_for_node(node)
@projects[node.document]
end
end
end
...@@ -6,7 +6,10 @@ describe Banzai::CommitRenderer do ...@@ -6,7 +6,10 @@ describe Banzai::CommitRenderer do
user = build(:user) user = build(:user)
project = create(:project, :repository) project = create(:project, :repository)
expect(Banzai::ObjectRenderer).to receive(:new).with(project, user).and_call_original expect(Banzai::ObjectRenderer)
.to receive(:new)
.with(user: user, default_project: project)
.and_call_original
described_class::ATTRIBUTES.each do |attr| described_class::ATTRIBUTES.each do |attr|
expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::IssuableExtractor do describe Banzai::IssuableExtractor do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:extractor) { described_class.new(project, user) } let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
let(:issue_link) do let(:issue_link) do
......
...@@ -3,7 +3,14 @@ require 'spec_helper' ...@@ -3,7 +3,14 @@ require 'spec_helper'
describe Banzai::ObjectRenderer do describe Banzai::ObjectRenderer do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { project.owner } let(:user) { project.owner }
let(:renderer) { described_class.new(project, user, custom_value: 'value') } let(:renderer) do
described_class.new(
default_project: project,
user: user,
redaction_context: { custom_value: 'value' }
)
end
let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) } let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) }
describe '#render' do describe '#render' do
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::Redactor do describe Banzai::Redactor do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { build(:project) } let(:project) { build(:project) }
let(:redactor) { described_class.new(project, user) } let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
describe '#redact' do describe '#redact' do
context 'when reference not visible to user' do context 'when reference not visible to user' do
...@@ -54,7 +54,7 @@ describe Banzai::Redactor do ...@@ -54,7 +54,7 @@ describe Banzai::Redactor do
context 'when project is in pending delete' do context 'when project is in pending delete' do
let!(:issue) { create(:issue, project: project) } let!(:issue) { create(:issue, project: project) }
let(:redactor) { described_class.new(project, user) } let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
before do before do
project.update(pending_delete: true) project.update(pending_delete: true)
......
...@@ -5,13 +5,14 @@ describe Banzai::ReferenceParser::BaseParser do ...@@ -5,13 +5,14 @@ describe Banzai::ReferenceParser::BaseParser do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:context) { Banzai::RenderContext.new(project, user) }
subject do subject do
klass = Class.new(described_class) do klass = Class.new(described_class) do
self.reference_type = :foo self.reference_type = :foo
end end
klass.new(project, user) klass.new(context)
end end
describe '.reference_type=' do describe '.reference_type=' do
...@@ -23,6 +24,19 @@ describe Banzai::ReferenceParser::BaseParser do ...@@ -23,6 +24,19 @@ describe Banzai::ReferenceParser::BaseParser do
end end
end end
describe '#project_for_node' do
it 'returns the Project for a node' do
document = instance_double('document', fragment?: false)
project = instance_double('project')
object = instance_double('object', project: project)
node = instance_double('node', document: document)
context.associate_document(document, object)
expect(subject.project_for_node(node)).to eq(project)
end
end
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
let(:link) { empty_html_link } let(:link) { empty_html_link }
...@@ -164,7 +178,7 @@ describe Banzai::ReferenceParser::BaseParser do ...@@ -164,7 +178,7 @@ describe Banzai::ReferenceParser::BaseParser do
self.reference_type = :test self.reference_type = :test
end end
instance = dummy.new(project, user) instance = dummy.new(Banzai::RenderContext.new(project, user))
document = Nokogiri::HTML.fragment('<a class="gfm"></a><a class="gfm" data-reference-type="test"></a>') document = Nokogiri::HTML.fragment('<a class="gfm"></a><a class="gfm" data-reference-type="test"></a>')
expect(instance).to receive(:gather_references) expect(instance).to receive(:gather_references)
......
...@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitParser do ...@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitParser do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
......
...@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do ...@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
......
...@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do ...@@ -5,7 +5,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
......
...@@ -7,7 +7,7 @@ describe Banzai::ReferenceParser::IssueParser do ...@@ -7,7 +7,7 @@ describe Banzai::ReferenceParser::IssueParser do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
context 'when the link has a data-issue attribute' do context 'when the link has a data-issue attribute' do
......
...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::LabelParser do ...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::LabelParser do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:label) { create(:label, project: project) } let(:label) { create(:label, project: project) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
......
...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do ...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
......
...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MilestoneParser do ...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::MilestoneParser do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
......
...@@ -9,7 +9,7 @@ describe Banzai::ReferenceParser::SnippetParser do ...@@ -9,7 +9,7 @@ describe Banzai::ReferenceParser::SnippetParser do
let(:external_user) { create(:user, :external) } let(:external_user) { create(:user, :external) }
let(:project_member) { create(:user) } let(:project_member) { create(:user) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
def visible_references(snippet_visibility, user = nil) def visible_references(snippet_visibility, user = nil)
......
...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::UserParser do ...@@ -6,7 +6,7 @@ describe Banzai::ReferenceParser::UserParser do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public, group: group, creator: user) } let(:project) { create(:project, :public, group: group, creator: user) }
subject { described_class.new(project, user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
describe '#referenced_by' do describe '#referenced_by' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Banzai::RenderContext do
let(:document) { Nokogiri::HTML.fragment('<p>hello</p>') }
describe '#project_for_node' do
it 'returns the default project if no associated project was found' do
project = instance_double('project')
context = described_class.new(project)
expect(context.project_for_node(document)).to eq(project)
end
it 'returns the associated project if one was associated explicitly' do
project = instance_double('project')
obj = instance_double('object', project: project)
context = described_class.new
context.associate_document(document, obj)
expect(context.project_for_node(document)).to eq(project)
end
it 'returns the project associated with a DocumentFragment when using a node' do
project = instance_double('project')
obj = instance_double('object', project: project)
context = described_class.new
node = document.children.first
context.associate_document(document, obj)
expect(context.project_for_node(node)).to eq(project)
end
end
end
...@@ -9,9 +9,7 @@ describe Events::RenderService do ...@@ -9,9 +9,7 @@ describe Events::RenderService do
context 'when the request format is atom' do context 'when the request format is atom' do
it 'renders the note inside events' do it 'renders the note inside events' do
expect(Banzai::ObjectRenderer).to receive(:new) expect(Banzai::ObjectRenderer).to receive(:new)
.with(event.project, user, .with(user: user, redaction_context: { only_path: false, xhtml: true })
only_path: false,
xhtml: true)
.and_call_original .and_call_original
expect_any_instance_of(Banzai::ObjectRenderer) expect_any_instance_of(Banzai::ObjectRenderer)
...@@ -24,7 +22,7 @@ describe Events::RenderService do ...@@ -24,7 +22,7 @@ describe Events::RenderService do
context 'when the request format is not atom' do context 'when the request format is not atom' do
it 'renders the note inside events' do it 'renders the note inside events' do
expect(Banzai::ObjectRenderer).to receive(:new) expect(Banzai::ObjectRenderer).to receive(:new)
.with(event.project, user, {}) .with(user: user, redaction_context: {})
.and_call_original .and_call_original
expect_any_instance_of(Banzai::ObjectRenderer) expect_any_instance_of(Banzai::ObjectRenderer)
......
...@@ -4,23 +4,28 @@ describe Notes::RenderService do ...@@ -4,23 +4,28 @@ describe Notes::RenderService do
describe '#execute' do describe '#execute' do
it 'renders a Note' do it 'renders a Note' do
note = double(:note) note = double(:note)
project = double(:project)
wiki = double(:wiki) wiki = double(:wiki)
user = double(:user) user = double(:user)
expect(Banzai::ObjectRenderer).to receive(:new) expect(Banzai::ObjectRenderer)
.with(project, user, .to receive(:new)
requested_path: 'foo', .with(
project_wiki: wiki, user: user,
ref: 'bar', redaction_context: {
only_path: nil, requested_path: 'foo',
xhtml: false) project_wiki: wiki,
ref: 'bar',
only_path: nil,
xhtml: false
}
)
.and_call_original .and_call_original
expect_any_instance_of(Banzai::ObjectRenderer) expect_any_instance_of(Banzai::ObjectRenderer)
.to receive(:render).with([note], :note) .to receive(:render)
.with([note], :note)
described_class.new(user).execute([note], project, described_class.new(user).execute([note],
requested_path: 'foo', requested_path: 'foo',
project_wiki: wiki, project_wiki: wiki,
ref: 'bar', ref: 'bar',
......
...@@ -18,6 +18,11 @@ module FilterSpecHelper ...@@ -18,6 +18,11 @@ module FilterSpecHelper
context.reverse_merge!(project: project) context.reverse_merge!(project: project)
end end
render_context = Banzai::RenderContext
.new(context[:project], context[:current_user])
context = context.merge(render_context: render_context)
described_class.call(html, context) described_class.call(html, context)
end end
......
...@@ -5,9 +5,11 @@ module ReferenceParserHelpers ...@@ -5,9 +5,11 @@ module ReferenceParserHelpers
shared_examples 'no N+1 queries' do shared_examples 'no N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user)
record_queries = lambda do |links| record_queries = lambda do |links|
ActiveRecord::QueryRecorder.new do ActiveRecord::QueryRecorder.new do
described_class.new(project, user).nodes_visible_to_user(user, links) described_class.new(context).nodes_visible_to_user(user, links)
end end
end end
...@@ -19,9 +21,11 @@ module ReferenceParserHelpers ...@@ -19,9 +21,11 @@ module ReferenceParserHelpers
end end
it 'avoids N+1 queries in #records_for_nodes', :request_store do it 'avoids N+1 queries in #records_for_nodes', :request_store do
context = Banzai::RenderContext.new(project, user)
record_queries = lambda do |links| record_queries = lambda do |links|
ActiveRecord::QueryRecorder.new do ActiveRecord::QueryRecorder.new do
described_class.new(project, user).records_for_nodes(links) described_class.new(context).records_for_nodes(links)
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment