From c0dfaf98ac60fc928a2df5f7f355b262f0fcaf91 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 14:47:03 +0530 Subject: A working implementation of a project reference filter which links project references to project profile. --- app/models/project.rb | 18 +++ lib/banzai/filter/project_reference_filter.rb | 114 ++++++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 1 + lib/banzai/reference_parser/project_parser.rb | 17 +++ .../banzai/filter/project_reference_filter_spec.rb | 149 +++++++++++++++++++++ .../banzai/reference_parser/project_parser_spec.rb | 30 +++++ spec/models/project_spec.rb | 9 ++ 7 files changed, 338 insertions(+) create mode 100644 lib/banzai/filter/project_reference_filter.rb create mode 100644 lib/banzai/reference_parser/project_parser.rb create mode 100644 spec/lib/banzai/filter/project_reference_filter_spec.rb create mode 100644 spec/lib/banzai/reference_parser/project_parser_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index d91d7dcfe9a..7a8e3f8a7ec 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -456,6 +456,20 @@ class Project < ActiveRecord::Base }x end + def reference_postfix + '>' + end + + # Pattern used to extract `project>` project references from text + # (?!\w) matches any non-word character + def markdown_reference_pattern + %r{ + #{reference_pattern} + (#{reference_postfix}|#{CGI.escapeHTML(reference_postfix)}) + (?!\w) + }x + end + def trending joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id') .reorder('trending_projects.id ASC') @@ -884,6 +898,10 @@ class Project < ActiveRecord::Base end end + def to_reference_with_postfix + "#{to_reference(full: true)}#{self.class.reference_postfix}" + end + # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb new file mode 100644 index 00000000000..ed69f7eae92 --- /dev/null +++ b/lib/banzai/filter/project_reference_filter.rb @@ -0,0 +1,114 @@ +module Banzai + module Filter + # HTML filter that replaces project references with links. + class ProjectReferenceFilter < ReferenceFilter + self.reference_type = :project + + # Public: Find `project>` project references in text + # + # ProjectReferenceFilter.references_in(text) do |match, project| + # "#{project}>" + # end + # + # text - String text to search. + # + # Yields the String match, and the String project name. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(Project.markdown_reference_pattern) do |match| + yield match, "#{$~[:namespace]}/#{$~[:project]}" + end + end + + def call + ref_pattern = Project.markdown_reference_pattern + ref_pattern_start = /\A#{ref_pattern}\z/ + + nodes.each do |node| + if text_node?(node) + replace_text_when_pattern_matches(node, ref_pattern) do |content| + project_link_filter(content) + end + elsif element_node?(node) + yield_valid_link(node) do |link, inner_html| + if link =~ ref_pattern_start + replace_link_node_with_href(node, link) do + project_link_filter(link, link_content: inner_html) + end + end + end + end + end + + doc + end + + # Replace `project>` project references in text with links to the referenced + # project page. + # + # text - String text to replace references in. + # link_content - Original content of the link being replaced. + # + # Returns a String with `project>` references replaced with links. All links + # have `gfm` and `gfm-project` class names attached for styling. + def project_link_filter(text, link_content: nil) + self.class.references_in(text) do |match, project_name| + cached_call(:banzai_url_for_object, match, path: [Project, project_name.downcase]) do + if project = projects_hash[project_name.downcase] + link_to_project(project, link_content: link_content) || match + else + match + end + end + end + end + + # Returns a Hash containing all Namespace objects for the project + # references in the current document. + # + # The keys of this Hash are the namespace paths, the values the + # corresponding Namespace objects. + def projects_hash + @projects ||= Project.where_full_path_in(projects) + .index_by(&:full_path) + .transform_keys(&:downcase) + end + + # Returns all projects referenced in the current document. + def projects + refs = Set.new + + nodes.each do |node| + node.to_html.scan(Project.markdown_reference_pattern) do + refs << "#{$~[:namespace]}/#{$~[:project]}" + end + end + + refs.to_a + end + + private + + def urls + Gitlab::Routing.url_helpers + end + + def link_class + reference_class(:project) + end + + def link_to_project(project, link_content: nil) + url = urls.project_url(project, only_path: context[:only_path]) + data = data_attribute(project: project.id) + content = link_content || project.full_path + Project.reference_postfix + + link_tag(url, data, content, project.name) + end + + def link_tag(url, data, link_content, title) + %(#{link_content}) + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 0d9b874ef85..9295ca9efcc 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -25,6 +25,7 @@ module Banzai Filter::ExternalLinkFilter, Filter::UserReferenceFilter, + Filter::ProjectReferenceFilter, Filter::IssueReferenceFilter, Filter::ExternalIssueReferenceFilter, Filter::MergeRequestReferenceFilter, diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb new file mode 100644 index 00000000000..54fd3c38a85 --- /dev/null +++ b/lib/banzai/reference_parser/project_parser.rb @@ -0,0 +1,17 @@ +module Banzai + module ReferenceParser + class ProjectParser < BaseParser + self.reference_type = :project + + def references_relation + Project + end + + private + + def can_read_reference?(user, ref_project, node) + can?(user, :read_project, ref_project) + end + end + end +end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb new file mode 100644 index 00000000000..2ace8496b08 --- /dev/null +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -0,0 +1,149 @@ +require 'spec_helper' + +describe Banzai::Filter::ProjectReferenceFilter do + include FilterSpecHelper + + def invalidate_reference(reference) + "#{reference.reverse}" + end + + def get_reference(project) + project.to_reference_with_postfix + end + + let(:project) { create(:project, :public) } + let(:reference) { get_reference(project) } + + it 'ignores invalid projects' do + exp = act = "Hey #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp)) + end + + it 'ignores references with text after the > sign' do + exp = act = "Hey #{reference}foo" + expect(reference_filter(act).to_html).to eq CGI.escapeHTML(exp) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Hey #{CGI.escapeHTML(reference)}" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'mentioning a project' do + it_behaves_like 'a reference containing an element node' + + it 'links to a Project' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'links to a Project with a period' do + project = create(:project, name: 'alphA.Beta') + + doc = reference_filter("Hey #{get_reference(project)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a Project with an underscore' do + project = create(:project, name: 'ping_pong_king') + + doc = reference_filter("Hey #{get_reference(project)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a Project with different case-sensitivity' do + project = create(:project, name: 'RescueRanger') + reference = get_reference(project) + + doc = reference_filter("Hey #{reference.upcase}") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').text).to eq(reference) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.namespace.owner_id.to_s + end + end + + it 'includes default classes' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip' + end + + it 'supports an :only_path context' do + doc = reference_filter("Hey #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.project_path(project) + end + + context 'referencing a project in a link href' do + let(:reference) { %Q{Project} } + + it 'links to a Project' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(%r{\(Project\.\)}) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + end + + context 'in group context' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + + let(:nested_group) { create(:group, :nested) } + let(:nested_project) { create(:project, group: nested_group) } + + it 'supports mentioning a project' do + reference = get_reference(project) + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'supports mentioning a project in a nested group' do + reference = get_reference(nested_project) + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.project_url(nested_project) + end + end + + describe '#projects_hash' do + it 'returns a Hash containing all Projects' do + document = Nokogiri::HTML.fragment("

#{get_reference(project)}

") + filter = described_class.new(document, project: project) + + expect(filter.projects_hash).to eq({ project.full_path => project }) + end + end + + describe '#projects' do + it 'returns the projects mentioned in a document' do + document = Nokogiri::HTML.fragment("

#{get_reference(project)}

") + filter = described_class.new(document, project: project) + + expect(filter.projects).to eq([project.full_path]) + end + end +end diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb new file mode 100644 index 00000000000..4a6c2294fbd --- /dev/null +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Banzai::ReferenceParser::ProjectParser do + include ReferenceParserHelpers + + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } + + describe '#referenced_by' do + describe 'when the link has a data-project attribute' do + context 'using an existing project ID' do + it 'returns an Array of projects' do + link['data-project'] = project.id.to_s + + expect(subject.referenced_by([link])).to eq([project]) + end + end + + context 'using a non-existing project ID' do + it 'returns an empty Array' do + link['data-project'] = '' + + expect(subject.referenced_by([link])).to eq([]) + end + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index abdc65336ca..acc7821a21e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,6 +344,15 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end + describe '#to_reference_with_postfix' do + it 'returns the full path with reference_postfix' do + namespace = create(:namespace, path: 'sample-namespace') + project = create(:project, path: 'sample-project', namespace: namespace) + + expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' + end + end + describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } -- cgit v1.2.3 From a4577cb5f9403d4e777de9d252854a78f3579cbb Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 16:11:10 +0530 Subject: Correct the "includes a data-project attribute" test in project_reference_filter_spec --- spec/lib/banzai/filter/project_reference_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 2ace8496b08..e5b18ee2cdb 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -68,7 +68,7 @@ describe Banzai::Filter::ProjectReferenceFilter do link = doc.css('a').first expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.namespace.owner_id.to_s + expect(link.attr('data-project')).to eq project.id.to_s end end -- cgit v1.2.3 From 3abe12707ee8c84aa7b4106db3092a554a31ab04 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 16:25:09 +0530 Subject: Add an entry about project reference linking in the markdown doc. --- doc/user/markdown.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 8e87c896a72..3906bed8682 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -259,6 +259,7 @@ GFM will recognize the following: | `@user_name` | specific user | | `@group_name` | specific group | | `@all` | entire team | +| `namespace/project>` | project | | `#12345` | issue | | `!123` | merge request | | `$123` | snippet | -- cgit v1.2.3 From dedbb67f101153fb72385c8077fda190d7f641fc Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 16:27:42 +0530 Subject: Add a comment explaining the need to check for the escaped form of '>' when searching for project references. --- app/models/project.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 7a8e3f8a7ec..fab496c02ba 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -462,6 +462,8 @@ class Project < ActiveRecord::Base # Pattern used to extract `project>` project references from text # (?!\w) matches any non-word character + # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped + # when the reference comes from an external source. def markdown_reference_pattern %r{ #{reference_pattern} -- cgit v1.2.3 From 28cf16a772d359cac3eb589553b579cdc2de6ca9 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sat, 30 Jun 2018 18:49:07 +0530 Subject: Correct the comment above the projects_hash function in the project_reference_filter --- lib/banzai/filter/project_reference_filter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index ed69f7eae92..4a162733438 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -64,11 +64,11 @@ module Banzai end end - # Returns a Hash containing all Namespace objects for the project + # Returns a Hash containing all Project objects for the project # references in the current document. # - # The keys of this Hash are the namespace paths, the values the - # corresponding Namespace objects. + # The keys of this Hash are the project paths, the values the + # corresponding Project objects. def projects_hash @projects ||= Project.where_full_path_in(projects) .index_by(&:full_path) -- cgit v1.2.3 From b74ed743e4e7bfb7570f3d3146ae2254a1fa8c9b Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 00:04:37 +0530 Subject: Correct the comment above the Project.markdown_reference_pattern method --- app/models/project.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index fab496c02ba..53608374d39 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -460,8 +460,8 @@ class Project < ActiveRecord::Base '>' end - # Pattern used to extract `project>` project references from text - # (?!\w) matches any non-word character + # Pattern used to extract `namespace/project>` project references from text. + # (?!\w) matches any non-word character. # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped # when the reference comes from an external source. def markdown_reference_pattern -- cgit v1.2.3 From 4fb466249fd06b87507244796286fbf56f65200a Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 00:16:25 +0530 Subject: Add a spec for a user viewing a reference to a private project --- .../banzai/reference_parser/project_parser_spec.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb index 4a6c2294fbd..c6a4d15e47c 100644 --- a/spec/lib/banzai/reference_parser/project_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -14,7 +14,7 @@ describe Banzai::ReferenceParser::ProjectParser do it 'returns an Array of projects' do link['data-project'] = project.id.to_s - expect(subject.referenced_by([link])).to eq([project]) + expect(subject.gather_references([link])).to eq([project]) end end @@ -22,7 +22,25 @@ describe Banzai::ReferenceParser::ProjectParser do it 'returns an empty Array' do link['data-project'] = '' - expect(subject.referenced_by([link])).to eq([]) + expect(subject.gather_references([link])).to eq([]) + end + end + + context 'using a private project ID' do + it 'returns an empty Array when unauthorized' do + private_project = create(:project, :private) + + link['data-project'] = private_project.id.to_s + + expect(subject.gather_references([link])).to eq([]) + end + + it 'returns an Array when authorized' do + private_project = create(:project, :private, namespace: user.namespace) + + link['data-project'] = private_project.id.to_s + + expect(subject.gather_references([link])).to eq([private_project]) end end end -- cgit v1.2.3 From e533826382b55705d1692d755c631ca08d4afd5d Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 00:36:34 +0530 Subject: Move common tests from user and project reference filter specs to a shared example --- .../banzai/filter/project_reference_filter_spec.rb | 74 +------------------ .../banzai/filter/user_reference_filter_spec.rb | 85 +++------------------- .../banzai/reference_filter_shared_examples.rb | 73 +++++++++++++++++++ 3 files changed, 89 insertions(+), 143 deletions(-) diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index e5b18ee2cdb..a57f4644866 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -12,8 +12,12 @@ describe Banzai::Filter::ProjectReferenceFilter do end let(:project) { create(:project, :public) } + subject { project } + let(:subject_name) { "project" } let(:reference) { get_reference(project) } + it_behaves_like 'user reference or project reference' + it 'ignores invalid projects' do exp = act = "Hey #{invalidate_reference(reference)}" @@ -32,81 +36,11 @@ describe Banzai::Filter::ProjectReferenceFilter do end end - context 'mentioning a project' do - it_behaves_like 'a reference containing an element node' - - it 'links to a Project' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) - end - - it 'links to a Project with a period' do - project = create(:project, name: 'alphA.Beta') - - doc = reference_filter("Hey #{get_reference(project)}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a Project with an underscore' do - project = create(:project, name: 'ping_pong_king') - - doc = reference_filter("Hey #{get_reference(project)}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a Project with different case-sensitivity' do - project = create(:project, name: 'RescueRanger') - reference = get_reference(project) - - doc = reference_filter("Hey #{reference.upcase}") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').text).to eq(reference) - end - - it 'includes a data-project attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - end - it 'includes default classes' do doc = reference_filter("Hey #{reference}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip' end - it 'supports an :only_path context' do - doc = reference_filter("Hey #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.project_path(project) - end - - context 'referencing a project in a link href' do - let(:reference) { %Q{Project} } - - it 'links to a Project' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) - end - - it 'links with adjacent text' do - doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(%r{\(Project\.\)}) - end - - it 'includes a data-project attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-project') - expect(link.attr('data-project')).to eq project.id.to_s - end - end - context 'in group context' do let(:group) { create(:group) } let(:project) { create(:project, group: group) } diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 2f86a046d28..334d29a5368 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -3,9 +3,17 @@ require 'spec_helper' describe Banzai::Filter::UserReferenceFilter do include FilterSpecHelper + def get_reference(user) + user.to_reference + end + let(:project) { create(:project, :public) } let(:user) { create(:user) } - let(:reference) { user.to_reference } + subject { user } + let(:subject_name) { "user" } + let(:reference) { get_reference(user) } + + it_behaves_like 'user reference or project reference' it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -66,45 +74,6 @@ describe Banzai::Filter::UserReferenceFilter do end end - context 'mentioning a user' do - it_behaves_like 'a reference containing an element node' - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links to a User with a period' do - user = create(:user, name: 'alphA.Beta') - - doc = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with an underscore' do - user = create(:user, name: 'ping_pong_king') - - doc = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with different case-sensitivity' do - user = create(:user, username: 'RescueRanger') - - doc = reference_filter("Hey #{user.to_reference.upcase}") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').text).to eq(user.to_reference) - end - - it 'includes a data-user attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - end - context 'mentioning a group' do it_behaves_like 'a reference containing an element node' @@ -154,36 +123,6 @@ describe Banzai::Filter::UserReferenceFilter do expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip' end - it 'supports an :only_path context' do - doc = reference_filter("Hey #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.user_path(user) - end - - context 'referencing a user in a link href' do - let(:reference) { %Q{User} } - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links with adjacent text' do - doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(%r{\(User\.\)}) - end - - it 'includes a data-user attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - end - context 'when a project is not specified' do let(:project) { nil } @@ -227,7 +166,7 @@ describe Banzai::Filter::UserReferenceFilter do end it 'supports mentioning a single user' do - reference = group_member.to_reference + reference = get_reference(group_member) doc = reference_filter("Hey #{reference}", context) expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member) @@ -243,7 +182,7 @@ describe Banzai::Filter::UserReferenceFilter do describe '#namespaces' do it 'returns a Hash containing all Namespaces' do - document = Nokogiri::HTML.fragment("

#{user.to_reference}

") + document = Nokogiri::HTML.fragment("

#{get_reference(user)}

") filter = described_class.new(document, project: project) ns = user.namespace @@ -253,7 +192,7 @@ describe Banzai::Filter::UserReferenceFilter do describe '#usernames' do it 'returns the usernames mentioned in a document' do - document = Nokogiri::HTML.fragment("

#{user.to_reference}

") + document = Nokogiri::HTML.fragment("

#{get_reference(user)}

") filter = described_class.new(document, project: project) expect(filter.usernames).to eq([user.username]) diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb index eb5da662ab5..476d80f3a93 100644 --- a/spec/support/banzai/reference_filter_shared_examples.rb +++ b/spec/support/banzai/reference_filter_shared_examples.rb @@ -11,3 +11,76 @@ shared_examples 'a reference containing an element node' do expect(doc.children.first.inner_html).to eq(inner_html) end end + +# Requires a reference, subject and subject_name: +# subject { create(:user) } +# let(:reference) { subject.to_reference } +# let(:subject_name) { 'user' } +shared_examples 'user reference or project reference' do + shared_examples 'it contains a data- attribute' do + it 'includes a data- attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute("data-#{subject_name}") + expect(link.attr("data-#{subject_name}")).to eq subject.id.to_s + end + end + + context 'mentioning a resource' do + it_behaves_like 'a reference containing an element node' + it_behaves_like 'it contains a data- attribute' + + it "links to a resource" do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.send("#{subject_name}_url", subject) + end + + it 'links to a resource with a period' do + subject = create(subject_name.to_sym, name: 'alphA.Beta') + + doc = reference_filter("Hey #{get_reference(subject)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a resource with an underscore' do + subject = create(subject_name.to_sym, name: 'ping_pong_king') + + doc = reference_filter("Hey #{get_reference(subject)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a resource with different case-sensitivity' do + subject = create(subject_name.to_sym, name: 'RescueRanger') + reference = get_reference(subject) + + doc = reference_filter("Hey #{reference.upcase}") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').text).to eq(reference) + end + end + + it 'supports an :only_path context' do + doc = reference_filter("Hey #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.send "#{subject_name}_path", subject + end + + context 'referencing a resource in a link href' do + let(:reference) { %Q{Some text} } + + it_behaves_like 'it contains a data- attribute' + + it 'links to the resource' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.send "#{subject_name}_url", subject + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(%r{\(Some text\.\)}) + end + end +end -- cgit v1.2.3 From 513d6a8457748295ad65cadbf946f125d81f2a4a Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 01:07:31 +0530 Subject: Use literal '>' as escaped form of '>' --- app/models/project.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 53608374d39..afeb4625cca 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -460,6 +460,10 @@ class Project < ActiveRecord::Base '>' end + def reference_postfix_escaped + '>' + end + # Pattern used to extract `namespace/project>` project references from text. # (?!\w) matches any non-word character. # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped @@ -467,7 +471,7 @@ class Project < ActiveRecord::Base def markdown_reference_pattern %r{ #{reference_pattern} - (#{reference_postfix}|#{CGI.escapeHTML(reference_postfix)}) + (#{reference_postfix}|#{reference_postfix_escaped}) (?!\w) }x end -- cgit v1.2.3 From 07ac9ee2f0861135e6cd514e956e6eb7019e7096 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 01:09:39 +0530 Subject: Correct the misleading name of a variable in ProjectReferenceFilter.project_link_filter --- lib/banzai/filter/project_reference_filter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 4a162733438..8bc4ceb92cd 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -53,9 +53,9 @@ module Banzai # Returns a String with `project>` references replaced with links. All links # have `gfm` and `gfm-project` class names attached for styling. def project_link_filter(text, link_content: nil) - self.class.references_in(text) do |match, project_name| - cached_call(:banzai_url_for_object, match, path: [Project, project_name.downcase]) do - if project = projects_hash[project_name.downcase] + self.class.references_in(text) do |match, project_path| + cached_call(:banzai_url_for_object, match, path: [Project, project_path.downcase]) do + if project = projects_hash[project_path.downcase] link_to_project(project, link_content: link_content) || match else match -- cgit v1.2.3 From c8302b2f50efad5c159fb9e01598259c80887dac Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 10:15:56 +0530 Subject: Correct the comments above the ProjectReferenceFilter#project_link_filter function to make it clear that the full project path is required in a reference. --- lib/banzai/filter/project_reference_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 8bc4ceb92cd..6f257a2d70d 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -44,13 +44,13 @@ module Banzai doc end - # Replace `project>` project references in text with links to the referenced + # Replace `namespace/project>` project references in text with links to the referenced # project page. # # text - String text to replace references in. # link_content - Original content of the link being replaced. # - # Returns a String with `project>` references replaced with links. All links + # Returns a String with `namespace/project>` references replaced with links. All links # have `gfm` and `gfm-project` class names attached for styling. def project_link_filter(text, link_content: nil) self.class.references_in(text) do |match, project_path| -- cgit v1.2.3 From 972f12218e6954e183d9654775c4cbedcc97d2eb Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 10:18:49 +0530 Subject: Eager load the namespace and route of a project in project_reference_filter.rb --- lib/banzai/filter/project_reference_filter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 6f257a2d70d..6eead2ab1c3 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -70,7 +70,8 @@ module Banzai # The keys of this Hash are the project paths, the values the # corresponding Project objects. def projects_hash - @projects ||= Project.where_full_path_in(projects) + @projects ||= Project.eager_load(:namespace, :route) + .where_full_path_in(projects) .index_by(&:full_path) .transform_keys(&:downcase) end -- cgit v1.2.3 From 6572e045acbf43139dfecfe0ed1b289dfa12b6fa Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 10:19:53 +0530 Subject: Add a changelog entry for MR 20285 - Add the ability to reference projects in markdown --- changelogs/unreleased/28930-add-project-reference-filter.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/28930-add-project-reference-filter.yml diff --git a/changelogs/unreleased/28930-add-project-reference-filter.yml b/changelogs/unreleased/28930-add-project-reference-filter.yml new file mode 100644 index 00000000000..c7679c5fe76 --- /dev/null +++ b/changelogs/unreleased/28930-add-project-reference-filter.yml @@ -0,0 +1,5 @@ +--- +title: Add the ability to reference projects in comments and other markdown text. +merge_request: 20285 +author: Reuben Pereira +type: added -- cgit v1.2.3 From 7429eb3b48515d05d3840b6ba8ca68581eeb5ebe Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 21:26:28 +0530 Subject: Eager load a project's route and its namespace with route in project_reference_filter --- lib/banzai/filter/project_reference_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 6eead2ab1c3..9e680d25b50 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -70,7 +70,7 @@ module Banzai # The keys of this Hash are the project paths, the values the # corresponding Project objects. def projects_hash - @projects ||= Project.eager_load(:namespace, :route) + @projects ||= Project.eager_load(:route, namespace: [:route]) .where_full_path_in(projects) .index_by(&:full_path) .transform_keys(&:downcase) -- cgit v1.2.3 From 9e9137a6607e95bbe1e113da02fb95c2b19d66f9 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 21:46:13 +0530 Subject: Remove the Project#to_reference_with_postfix method since it is only used in the project_reference_filter_spec --- app/models/project.rb | 4 ---- spec/lib/banzai/filter/project_reference_filter_spec.rb | 2 +- spec/models/project_spec.rb | 9 --------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index afeb4625cca..79beec77843 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -904,10 +904,6 @@ class Project < ActiveRecord::Base end end - def to_reference_with_postfix - "#{to_reference(full: true)}#{self.class.reference_postfix}" - end - # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index a57f4644866..559293d5ac3 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -8,7 +8,7 @@ describe Banzai::Filter::ProjectReferenceFilter do end def get_reference(project) - project.to_reference_with_postfix + "#{project.to_reference(full: true)}#{Project.reference_postfix}" end let(:project) { create(:project, :public) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index acc7821a21e..abdc65336ca 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,15 +344,6 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end - describe '#to_reference_with_postfix' do - it 'returns the full path with reference_postfix' do - namespace = create(:namespace, path: 'sample-namespace') - project = create(:project, path: 'sample-project', namespace: namespace) - - expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' - end - end - describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } -- cgit v1.2.3 From d6f4810ea154d2f2303591ac37d616c8249fca9d Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 3 Jul 2018 22:24:59 +0530 Subject: Correct the comment above the ProjectReferenceFilter.references_in method. --- lib/banzai/filter/project_reference_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 9e680d25b50..9764cfa4ef5 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -4,7 +4,7 @@ module Banzai class ProjectReferenceFilter < ReferenceFilter self.reference_type = :project - # Public: Find `project>` project references in text + # Public: Find `namespace/project>` project references in text # # ProjectReferenceFilter.references_in(text) do |match, project| # "#{project}>" -- cgit v1.2.3 From 77c53f61262e01197c89f5c59f627c38df83be53 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Wed, 4 Jul 2018 21:41:46 +0530 Subject: Remove the restriction preventing project references with text adjacent to the > character --- app/models/project.rb | 2 -- spec/lib/banzai/filter/project_reference_filter_spec.rb | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 79beec77843..9dfa9a144fd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -465,14 +465,12 @@ class Project < ActiveRecord::Base end # Pattern used to extract `namespace/project>` project references from text. - # (?!\w) matches any non-word character. # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped # when the reference comes from an external source. def markdown_reference_pattern %r{ #{reference_pattern} (#{reference_postfix}|#{reference_postfix_escaped}) - (?!\w) }x end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 559293d5ac3..20f809e010e 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -24,9 +24,9 @@ describe Banzai::Filter::ProjectReferenceFilter do expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp)) end - it 'ignores references with text after the > sign' do - exp = act = "Hey #{reference}foo" - expect(reference_filter(act).to_html).to eq CGI.escapeHTML(exp) + it 'allows references with text after the > character' do + doc = reference_filter("Hey #{reference}foo") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(subject) end %w(pre code a style).each do |elem| -- cgit v1.2.3 From 2730ae1d869af4ddd48dc312d230c1bcafec19b5 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 5 Jul 2018 01:26:09 +0530 Subject: Use a custom ProjectParser#nodes_visible_to_user function so that the user permissions for all project references can be checked together --- lib/banzai/reference_parser/project_parser.rb | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index 54fd3c38a85..787cb671b2d 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -7,10 +7,29 @@ module Banzai Project end + def nodes_visible_to_user(user, nodes) + nodes_projects_hash = lazy { projects_for_nodes(nodes) } + project_attr = 'data-project' + + readable_project_ids = projects_readable_by_user(nodes_projects_hash.values, user) + + nodes.select do |node| + if node.has_attribute?(project_attr) + readable_project_ids.include?(nodes_projects_hash[node].try(:id)) + else + true + end + end + end + private - def can_read_reference?(user, ref_project, node) - can?(user, :read_project, ref_project) + # Returns an Array of Project ids that can be read by the given user. + # + # projects - The projects to reduce down to those readable by the user. + # user - The User for which to check the projects + def projects_readable_by_user(projects, user) + Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.collect(&:id)).pluck(:id) end end end -- cgit v1.2.3 From 74c81cc50e7d4deff11e9777b9522f8adf8b3c96 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 5 Jul 2018 01:55:55 +0530 Subject: Add back the Project#to_reference_with_postfix function since it can be used in the ProjectReferenceFilter#link_to_project function --- app/models/project.rb | 4 ++++ lib/banzai/filter/project_reference_filter.rb | 2 +- spec/lib/banzai/filter/project_reference_filter_spec.rb | 2 +- spec/models/project_spec.rb | 9 +++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 9dfa9a144fd..cf740de9405 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -902,6 +902,10 @@ class Project < ActiveRecord::Base end end + def to_reference_with_postfix + "#{to_reference(full: true)}#{self.class.reference_postfix}" + end + # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) if full || cross_namespace_reference?(from) diff --git a/lib/banzai/filter/project_reference_filter.rb b/lib/banzai/filter/project_reference_filter.rb index 9764cfa4ef5..fd2a86a6d45 100644 --- a/lib/banzai/filter/project_reference_filter.rb +++ b/lib/banzai/filter/project_reference_filter.rb @@ -102,7 +102,7 @@ module Banzai def link_to_project(project, link_content: nil) url = urls.project_url(project, only_path: context[:only_path]) data = data_attribute(project: project.id) - content = link_content || project.full_path + Project.reference_postfix + content = link_content || project.to_reference_with_postfix link_tag(url, data, content, project.name) end diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 20f809e010e..13e1fc2d3e2 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -8,7 +8,7 @@ describe Banzai::Filter::ProjectReferenceFilter do end def get_reference(project) - "#{project.to_reference(full: true)}#{Project.reference_postfix}" + project.to_reference_with_postfix end let(:project) { create(:project, :public) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index abdc65336ca..acc7821a21e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -344,6 +344,15 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end + describe '#to_reference_with_postfix' do + it 'returns the full path with reference_postfix' do + namespace = create(:namespace, path: 'sample-namespace') + project = create(:project, path: 'sample-project', namespace: namespace) + + expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' + end + end + describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } -- cgit v1.2.3 From a08b5144fe64cf7afde116334c91e95f54c9f4fb Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Thu, 5 Jul 2018 21:13:46 +0530 Subject: Use map instead of collect in the ProjectParser#projects_readable_by_user function --- lib/banzai/reference_parser/project_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index 787cb671b2d..2bf02e3df53 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -29,7 +29,7 @@ module Banzai # projects - The projects to reduce down to those readable by the user. # user - The User for which to check the projects def projects_readable_by_user(projects, user) - Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.collect(&:id)).pluck(:id) + Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) end end end -- cgit v1.2.3 From 8cc646fa0ec05820c83a163df74605f04c8877ea Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Sun, 15 Jul 2018 17:25:04 +0530 Subject: Remove nodes_visible_to_user from ProjectParser and memoize readable_project_ids --- lib/banzai/reference_parser/project_parser.rb | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index 2bf02e3df53..a329af42681 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -1,35 +1,28 @@ module Banzai module ReferenceParser class ProjectParser < BaseParser + include Gitlab::Utils::StrongMemoize + self.reference_type = :project def references_relation Project end - def nodes_visible_to_user(user, nodes) - nodes_projects_hash = lazy { projects_for_nodes(nodes) } - project_attr = 'data-project' - - readable_project_ids = projects_readable_by_user(nodes_projects_hash.values, user) - - nodes.select do |node| - if node.has_attribute?(project_attr) - readable_project_ids.include?(nodes_projects_hash[node].try(:id)) - else - true - end - end - end - private # Returns an Array of Project ids that can be read by the given user. # # projects - The projects to reduce down to those readable by the user. # user - The User for which to check the projects - def projects_readable_by_user(projects, user) - Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) + def readable_project_ids_for(projects, user) + strong_memoize(:readable_project_ids_for) do + Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) + end + end + + def can_read_reference?(user, ref_project, node) + readable_project_ids_for(@projects_for_nodes.values, user).include?(ref_project.try(:id)) end end end -- cgit v1.2.3 From 34e912b538b54619920b714b5177798597758808 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Tue, 31 Jul 2018 00:48:59 +0530 Subject: Use a hash to memoize readable_project_ids with user objects as keys --- lib/banzai/reference_parser/project_parser.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/banzai/reference_parser/project_parser.rb b/lib/banzai/reference_parser/project_parser.rb index a329af42681..2a33b00ddbd 100644 --- a/lib/banzai/reference_parser/project_parser.rb +++ b/lib/banzai/reference_parser/project_parser.rb @@ -13,16 +13,15 @@ module Banzai # Returns an Array of Project ids that can be read by the given user. # - # projects - The projects to reduce down to those readable by the user. # user - The User for which to check the projects - def readable_project_ids_for(projects, user) - strong_memoize(:readable_project_ids_for) do - Project.public_or_visible_to_user(user).where("projects.id IN (?)", projects.map(&:id)).pluck(:id) - end + def readable_project_ids_for(user) + @project_ids_by_user ||= {} + @project_ids_by_user[user] ||= + Project.public_or_visible_to_user(user).where("projects.id IN (?)", @projects_for_nodes.values.map(&:id)).pluck(:id) end def can_read_reference?(user, ref_project, node) - readable_project_ids_for(@projects_for_nodes.values, user).include?(ref_project.try(:id)) + readable_project_ids_for(user).include?(ref_project.try(:id)) end end end -- cgit v1.2.3