diff options
author | Adam Buckland <adamjbuckland@gmail.com> | 2016-08-24 18:11:48 +0100 |
---|---|---|
committer | Adam Niedzielski <adamsunday@gmail.com> | 2017-01-24 16:43:58 +0100 |
commit | c6e454baba650346f549e0a3c33f9c7d1e68fad0 (patch) | |
tree | 54d3b33fefc2cee3d3ae634c99bad66b3b16a31c | |
parent | c474ceb555febbf60f4134d0b46a15de2e850fa1 (diff) | |
download | gitlab-ce-adam_1369_strikethrough_closed_issues.tar.gz |
Added indication for closed issues in notesadam_1369_strikethrough_closed_issues
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 note being displayed.
Also as 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.
-rw-r--r-- | lib/banzai/filter/issuable_state_filter.rb | 41 | ||||
-rw-r--r-- | lib/banzai/filter/redactor_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/object_renderer.rb | 4 | ||||
-rw-r--r-- | lib/banzai/pipeline/post_process_pipeline.rb | 3 | ||||
-rw-r--r-- | lib/banzai/reference_parser/base_parser.rb | 20 | ||||
-rw-r--r-- | lib/banzai/reference_parser/issue_parser.rb | 10 | ||||
-rw-r--r-- | lib/banzai/reference_parser/merge_request_parser.rb | 20 | ||||
-rw-r--r-- | lib/banzai/reference_parser/user_parser.rb | 6 | ||||
-rw-r--r-- | spec/lib/banzai/filter/issuable_state_filter_spec.rb | 71 | ||||
-rw-r--r-- | spec/lib/banzai/filter/redactor_filter_spec.rb | 10 | ||||
-rw-r--r-- | spec/lib/banzai/object_renderer_spec.rb | 23 | ||||
-rw-r--r-- | spec/lib/banzai/reference_parser/base_parser_spec.rb | 18 | ||||
-rw-r--r-- | spec/lib/banzai/reference_parser/issue_parser_spec.rb | 2 |
13 files changed, 201 insertions, 29 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..75d23c11dd7 --- /dev/null +++ b/lib/banzai/filter/issuable_state_filter.rb @@ -0,0 +1,41 @@ +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 + def call + nodes = Querying.css(doc, 'a.gfm[data-reference-type=merge_request]') + nodes += Querying.css(doc, 'a.gfm[data-reference-type=issue]') + return doc unless nodes.count > 0 + + issue_parser = Banzai::ReferenceParser::IssueParser.new(project, current_user) + mr_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, current_user) + + issuables = issue_parser.issues_for_nodes(nodes) + issuables = issuables.merge(mr_parser.merge_requests_for_nodes(nodes)) + + nodes.each do |node| + issuable = issuables[node] + if issuable && issuable.closed? + node.children.last.content += ' [closed]' + 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/object_renderer.rb b/lib/banzai/object_renderer.rb index 9f8eb0931b8..10e7ab996e8 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -58,7 +58,7 @@ 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 = context.merge(object.banzai_render_context(attribute)).merge(skip_redaction: true) context end @@ -70,7 +70,7 @@ module Banzai string = Banzai.render_field(object, attribute) context = context_for(object, attribute) - Banzai::Pipeline[:relative_link].to_document(string, context) + Banzai::Pipeline[:post_process].to_document(string, context) end end diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb index ecff094b1e5..a22b750041c 100644 --- a/lib/banzai/pipeline/post_process_pipeline.rb +++ b/lib/banzai/pipeline/post_process_pipeline.rb @@ -4,7 +4,8 @@ module Banzai def self.filters FilterArray[ Filter::RelativeLinkFilter, - Filter::RedactorFilter + Filter::RedactorFilter, + Filter::IssuableStateFilter ] end diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index d8a855ec1fe..f61013904f9 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. # @@ -131,11 +130,14 @@ module Banzai def grouped_objects_for_nodes(nodes, collection, attribute) return {} if nodes.empty? - ids = unique_attribute_values(nodes, attribute) - rows = collection_objects_for_ids(collection, ids) + ids = unique_attribute_values(nodes, attribute).sort_by(&:to_i) + rows = collection_objects_for_ids(collection, ids).sort_by(&:id) + objects_by_id = Hash[ids.zip(rows)] - rows.each_with_object({}) do |row, hash| - hash[row.id] = row + nodes.each_with_object({}) do |node, hash| + if node.has_attribute?(attribute) + hash[node] = objects_by_id[node.attr(attribute)] + end end end diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index 6c20dec5734..4196145749c 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] }.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..8ce6618b835 100644 --- a/lib/banzai/reference_parser/merge_request_parser.rb +++ b/lib/banzai/reference_parser/merge_request_parser.rb @@ -3,6 +3,26 @@ 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.includes( + :author, + :assignee, + # These associations are primarily used for checking permissions. + # Eager loading these ensures we don't end up running dozens of + # queries in this process. + target_project: [ + { namespace: :owner }, + { group: [:owners, :group_members] }, + :invited_groups, + :project_members + ] + ), + 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 diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb new file mode 100644 index 00000000000..fb01b341866 --- /dev/null +++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Banzai::Filter::IssuableStateFilter, lib: true do + include ActionView::Helpers::UrlHelper + include FilterSpecHelper + + let(:user) { create(:user) } + + def create_link(data) + link_to('text', '', class: 'gfm', data: data) + end + + it 'ignores non-GFM links' do + html = %(See <a href="https://google.com/">Google</a>) + doc = filter(html, current_user: user) + expect(doc.css('a').last.text).not_to include('[closed]') + end + + it 'ignores non-issuable links' do + project = create(:empty_project, :public) + link = create_link(project: project, reference_type: 'issue') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).not_to include('[closed]') + end + + context 'for issue references' do + it 'ignores open issue references' do + issue = create(:issue) + link = create_link(issue: issue.id, reference_type: 'issue') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).not_to include('[closed]') + end + + it 'ignores reopened issue references' do + reopened_issue = create(:issue, :reopened) + link = create_link(issue: reopened_issue.id, reference_type: 'issue') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).not_to include('[closed]') + end + + it 'appends [closed] to closed issue references' do + closed_issue = create(:issue, :closed) + link = create_link(issue: closed_issue.id, reference_type: 'issue') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).to include('[closed]') + end + end + + context 'for merge request references' do + it 'ignores open merge request references' do + mr = create(:merge_request) + link = create_link(merge_request: mr.id, reference_type: 'merge_request') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).not_to include('[closed]') + end + + it 'ignores reopened merge request references' do + mr = create(:merge_request, :reopened) + link = create_link(merge_request: mr.id, reference_type: 'merge_request') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).not_to include('[closed]') + end + + it 'appends [closed] to closed merge request references' do + mr = create(:merge_request, :closed) + link = create_link(merge_request: mr.id, reference_type: 'merge_request') + doc = filter(link, current_user: user) + expect(doc.css('a').last.text).to include('[closed]') + end + end +end diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb index 0140a91c7ba..8a6fe1ad6a3 100644 --- a/spec/lib/banzai/filter/redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -15,6 +15,16 @@ describe Banzai::Filter::RedactorFilter, lib: true do link_to('text', '', class: 'gfm', data: data) end + it 'skips when the skip_redaction flag is set' do + user = create(:user) + project = create(:empty_project) + + link = reference_link(project: project.id, reference_type: 'test') + doc = filter(link, current_user: user, skip_redaction: true) + + expect(doc.css('a').length).to eq 1 + end + context 'with data-project' do let(:parser_class) do Class.new(Banzai::ReferenceParser::BaseParser) do diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 6bcda87c999..74a964198bb 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -78,6 +78,29 @@ describe Banzai::ObjectRenderer do expect(context).to have_key(:foo) expect(context[:foo]).to eq(:bar) end + + it 'adds the :skip_redaction flag' do + context = renderer.context_for(object, :note) + expect(context[:skip_redaction]).to eq(true) + end + + context 'when the object responds to "author"' do + it 'includes the author in the context' do + expect(object).to receive(:author).and_return('Alice') + + context = renderer.context_for(object, :note) + + expect(context[:author]).to eq('Alice') + end + end + + context 'when the object does not respond to "author"' do + it 'does not include the author in the context' do + context = renderer.context_for(object, :note) + + expect(context.key?(:author)).to eq(false) + end + end end describe '#render_attributes' do diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index aa127f0179d..a3141894c74 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -92,16 +92,26 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do end describe '#grouped_objects_for_nodes' do - it 'returns a Hash grouping objects per ID' do - nodes = [double(:node)] + it 'returns a Hash grouping objects per node' do + link = double(:link) + + expect(link).to receive(:has_attribute?). + with('data-user'). + and_return(true) + + expect(link).to receive(:attr). + with('data-user'). + and_return(user.id.to_s) + + nodes = [link] expect(subject).to receive(:unique_attribute_values). with(nodes, 'data-user'). - and_return([user.id]) + and_return([user.id.to_s]) hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user') - expect(hash).to eq({ user.id => user }) + expect(hash).to eq({ link => user }) end it 'returns an empty Hash when the list of nodes is empty' do diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index 6873b7b85f9..f98fa00c418 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -75,7 +75,7 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do link['data-issue'] = issue.id.to_s nodes = [link] - expect(subject.issues_for_nodes(nodes)).to eq({ issue.id => issue }) + expect(subject.issues_for_nodes(nodes)).to eq({ link => issue }) end end end |