Commit 7ecc247a authored by Brett Walker's avatar Brett Walker

Adjust class names to new references dir

for reference filters
parent dceae94f
......@@ -3,12 +3,14 @@
module EE
module Banzai
module Filter
module AbstractReferenceFilter
extend ::Gitlab::Utils::Override
module References
module AbstractReferenceFilter
extend ::Gitlab::Utils::Override
override :current_project_namespace_path
def current_project_namespace_path
@current_project_namespace_path ||= (project&.namespace || group)&.full_path
override :current_project_namespace_path
def current_project_namespace_path
@current_project_namespace_path ||= (project&.namespace || group)&.full_path
end
end
end
end
......
......@@ -3,51 +3,53 @@
module EE
module Banzai
module Filter
# HTML filter that replaces epic references with links. References to
# epics that do not exist are ignored.
#
# This filter supports cross-project/group references.
module EpicReferenceFilter
extend ActiveSupport::Concern
module References
# HTML filter that replaces epic references with links. References to
# epics that do not exist are ignored.
#
# This filter supports cross-project/group references.
module EpicReferenceFilter
extend ActiveSupport::Concern
class_methods do
def references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
symbol = $~[object_sym]
if object_class.reference_valid?(symbol)
yield match, symbol.to_i, nil, $~[:group], $~
else
match
class_methods do
def references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
symbol = $~[object_sym]
if object_class.reference_valid?(symbol)
yield match, symbol.to_i, nil, $~[:group], $~
else
match
end
end
end
end
end
def url_for_object(epic, group)
urls = ::Gitlab::Routing.url_helpers
urls.group_epic_url(group, epic, only_path: context[:only_path])
end
def url_for_object(epic, group)
urls = ::Gitlab::Routing.url_helpers
urls.group_epic_url(group, epic, only_path: context[:only_path])
end
def data_attributes_for(text, group, object, link_content: false, link_reference: false)
{
original: escape_html_entities(text),
link: link_content,
link_reference: link_reference,
group: group.id,
object_sym => object.id
}
end
def data_attributes_for(text, group, object, link_content: false, link_reference: false)
{
original: escape_html_entities(text),
link: link_content,
link_reference: link_reference,
group: group.id,
object_sym => object.id
}
end
# rubocop: disable CodeReuse/ActiveRecord
def parent_records(parent, ids)
parent.epics.where(iid: ids.to_a)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def parent_records(parent, ids)
parent.epics.where(iid: ids.to_a)
end
# rubocop: enable CodeReuse/ActiveRecord
private
private
def parent_type
:group
def parent_type
:group
end
end
end
end
......
......@@ -3,113 +3,115 @@
module EE
module Banzai
module Filter
# HTML filter that replaces iteration references with links.
module IterationReferenceFilter
include ::Gitlab::Utils::StrongMemoize
module References
# HTML filter that replaces iteration references with links.
module IterationReferenceFilter
include ::Gitlab::Utils::StrongMemoize
def find_object(parent, id)
return unless valid_context?(parent)
def find_object(parent, id)
return unless valid_context?(parent)
find_iteration(parent, id: id)
end
def valid_context?(parent)
group_context?(parent) || project_context?(parent)
end
def group_context?(parent)
strong_memoize(:group_context) do
parent.is_a?(Group)
find_iteration(parent, id: id)
end
end
def project_context?(parent)
strong_memoize(:project_context) do
parent.is_a?(Project)
def valid_context?(parent)
group_context?(parent) || project_context?(parent)
end
end
def references_in(text, pattern = ::Iteration.reference_pattern)
# We'll handle here the references that follow the `reference_pattern`.
# Other patterns (for example, the link pattern) are handled by the
# default implementation.
return super(text, pattern) if pattern != ::Iteration.reference_pattern
iterations = {}
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
iteration = parse_and_find_iteration($~[:project], $~[:namespace], $~[:iteration_id], $~[:iteration_name])
def group_context?(parent)
strong_memoize(:group_context) do
parent.is_a?(Group)
end
end
if iteration
iterations[iteration.id] = yield match, iteration.id, $~[:project], $~[:namespace], $~
"#{::Banzai::Filter::AbstractReferenceFilter::REFERENCE_PLACEHOLDER}#{iteration.id}"
else
match
def project_context?(parent)
strong_memoize(:project_context) do
parent.is_a?(Project)
end
end
return text if iterations.empty?
def references_in(text, pattern = ::Iteration.reference_pattern)
# We'll handle here the references that follow the `reference_pattern`.
# Other patterns (for example, the link pattern) are handled by the
# default implementation.
return super(text, pattern) if pattern != ::Iteration.reference_pattern
iterations = {}
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
iteration = parse_and_find_iteration($~[:project], $~[:namespace], $~[:iteration_id], $~[:iteration_name])
if iteration
iterations[iteration.id] = yield match, iteration.id, $~[:project], $~[:namespace], $~
"#{::Banzai::Filter::References::AbstractReferenceFilter::REFERENCE_PLACEHOLDER}#{iteration.id}"
else
match
end
end
escape_with_placeholders(unescaped_html, iterations)
end
return text if iterations.empty?
def parse_and_find_iteration(project_ref, namespace_ref, iteration_id, iteration_name)
project_path = full_project_path(namespace_ref, project_ref)
escape_with_placeholders(unescaped_html, iterations)
end
# Returns group if project is not found by path
parent = parent_from_ref(project_path)
def parse_and_find_iteration(project_ref, namespace_ref, iteration_id, iteration_name)
project_path = full_project_path(namespace_ref, project_ref)
return unless parent
# Returns group if project is not found by path
parent = parent_from_ref(project_path)
iteration_params = iteration_params(iteration_id, iteration_name)
return unless parent
find_iteration(parent, iteration_params)
end
iteration_params = iteration_params(iteration_id, iteration_name)
def iteration_params(id, name)
if name
{ name: name.tr('"', '') }
else
{ id: id.to_i }
find_iteration(parent, iteration_params)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def find_iteration(parent, params)
::Iteration.for_projects_and_groups(project_ids(parent), group_and_ancestors_ids(parent)).find_by(**params)
end
# rubocop: enable CodeReuse/ActiveRecord
def iteration_params(id, name)
if name
{ name: name.tr('"', '') }
else
{ id: id.to_i }
end
end
def project_ids(parent)
parent.id if project_context?(parent)
end
# rubocop: disable CodeReuse/ActiveRecord
def find_iteration(parent, params)
::Iteration.for_projects_and_groups(project_ids(parent), group_and_ancestors_ids(parent)).find_by(**params)
end
# rubocop: enable CodeReuse/ActiveRecord
def group_and_ancestors_ids(parent)
if group_context?(parent)
parent.self_and_ancestors.select(:id)
elsif project_context?(parent)
parent.group&.self_and_ancestors&.select(:id)
def project_ids(parent)
parent.id if project_context?(parent)
end
end
def url_for_object(iteration, _parent)
::Gitlab::Routing
.url_helpers
.iteration_url(iteration, only_path: context[:only_path])
end
def group_and_ancestors_ids(parent)
if group_context?(parent)
parent.self_and_ancestors.select(:id)
elsif project_context?(parent)
parent.group&.self_and_ancestors&.select(:id)
end
end
def url_for_object(iteration, _parent)
::Gitlab::Routing
.url_helpers
.iteration_url(iteration, only_path: context[:only_path])
end
def object_link_text(object, matches)
iteration_link = escape_once(super)
reference = object.project&.to_reference_base(project)
def object_link_text(object, matches)
iteration_link = escape_once(super)
reference = object.project&.to_reference_base(project)
if reference.present?
"#{iteration_link} <i>in #{reference}</i>".html_safe
else
iteration_link
if reference.present?
"#{iteration_link} <i>in #{reference}</i>".html_safe
else
iteration_link
end
end
end
def object_link_title(_object, _matches)
'Iteration'
def object_link_title(_object, _matches)
'Iteration'
end
end
end
end
......
......@@ -3,24 +3,26 @@
module EE
module Banzai
module Filter
module LabelReferenceFilter
extend ::Gitlab::Utils::Override
module References
module LabelReferenceFilter
extend ::Gitlab::Utils::Override
override :data_attributes_for
def data_attributes_for(text, parent, object, link_content: false, link_reference: false)
return super unless object.scoped_label?
override :data_attributes_for
def data_attributes_for(text, parent, object, link_content: false, link_reference: false)
return super unless object.scoped_label?
# Enabling HTML tooltips for scoped labels here and additional escaping is done in `object_link_title`
super.merge!(
html: true
)
end
# Enabling HTML tooltips for scoped labels here and additional escaping is done in `object_link_title`
super.merge!(
html: true
)
end
override :object_link_title
def object_link_title(object, matches)
return super unless object.scoped_label?
override :object_link_title
def object_link_title(object, matches)
return super unless object.scoped_label?
ERB::Util.html_escape(super)
ERB::Util.html_escape(super)
end
end
end
end
......
......@@ -3,61 +3,63 @@
module EE
module Banzai
module Filter
# HTML filter that replaces vulnerability references with links. References to
# vulnerabilities that do not exist are ignored.
#
# This filter supports cross-project/group references.
module VulnerabilityReferenceFilter
extend ActiveSupport::Concern
module References
# HTML filter that replaces vulnerability references with links. References to
# vulnerabilities that do not exist are ignored.
#
# This filter supports cross-project/group references.
module VulnerabilityReferenceFilter
extend ActiveSupport::Concern
class_methods do
def references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
symbol = $~[object_sym]
if object_class.reference_valid?(symbol)
yield match, symbol.to_i, $~[:project], $~[:namespace], $~
else
match
class_methods do
def references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
symbol = $~[object_sym]
if object_class.reference_valid?(symbol)
yield match, symbol.to_i, $~[:project], $~[:namespace], $~
else
match
end
end
end
end
end
def unescape_link(href)
return href if href =~ object_class.reference_pattern
def unescape_link(href)
return href if href =~ object_class.reference_pattern
super
end
super
end
def url_for_object(vulnerability, project)
urls = ::Gitlab::Routing.url_helpers
urls.project_security_vulnerability_url(project, vulnerability, only_path: context[:only_path])
end
def url_for_object(vulnerability, project)
urls = ::Gitlab::Routing.url_helpers
urls.project_security_vulnerability_url(project, vulnerability, only_path: context[:only_path])
end
def data_attributes_for(text, project, object, link_content: false, link_reference: false)
{
original: escape_html_entities(text),
link: link_content,
link_reference: link_reference,
project: project.id,
object_sym => object.id
}
end
def data_attributes_for(text, project, object, link_content: false, link_reference: false)
{
original: escape_html_entities(text),
link: link_content,
link_reference: link_reference,
project: project.id,
object_sym => object.id
}
end
def parent_records(parent, ids)
return ::Vulnerability.none if ids.blank? || parent.nil?
def parent_records(parent, ids)
return ::Vulnerability.none if ids.blank? || parent.nil?
parent.vulnerabilities.id_in(ids.to_a)
end
parent.vulnerabilities.id_in(ids.to_a)
end
def record_identifier(record)
record.id.to_i
end
def record_identifier(record)
record.id.to_i
end
private
private
def parent_type
:project
def parent_type
:project
end
end
end
end
......
......@@ -16,9 +16,9 @@ module EE
def reference_filters
[
::Banzai::Filter::EpicReferenceFilter,
::Banzai::Filter::IterationReferenceFilter,
::Banzai::Filter::VulnerabilityReferenceFilter,
::Banzai::Filter::References::EpicReferenceFilter,
::Banzai::Filter::References::IterationReferenceFilter,
::Banzai::Filter::References::VulnerabilityReferenceFilter,
*super
]
end
......
......@@ -9,9 +9,9 @@ module EE
class_methods do
def reference_filters
[
::Banzai::Filter::EpicReferenceFilter,
::Banzai::Filter::IterationReferenceFilter,
::Banzai::Filter::VulnerabilityReferenceFilter,
::Banzai::Filter::References::EpicReferenceFilter,
::Banzai::Filter::References::IterationReferenceFilter,
::Banzai::Filter::References::VulnerabilityReferenceFilter,
*super
]
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::EpicReferenceFilter do
RSpec.describe Banzai::Filter::References::EpicReferenceFilter do
include FilterSpecHelper
let(:urls) { Gitlab::Routing.url_helpers }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::IterationReferenceFilter do
RSpec.describe Banzai::Filter::References::IterationReferenceFilter do
include FilterSpecHelper
let(:parent_group) { create(:group, :public) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::LabelReferenceFilter do
RSpec.describe Banzai::Filter::References::LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, name: 'sample-project') }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::VulnerabilityReferenceFilter do
RSpec.describe Banzai::Filter::References::VulnerabilityReferenceFilter do
include FilterSpecHelper
let(:urls) { Gitlab::Routing.url_helpers }
......
......@@ -2,27 +2,29 @@
module Banzai
module Filter
class AlertReferenceFilter < IssuableReferenceFilter
self.reference_type = :alert
module References
class AlertReferenceFilter < IssuableReferenceFilter
self.reference_type = :alert
def self.object_class
AlertManagement::Alert
end
def self.object_class
AlertManagement::Alert
end
def self.object_sym
:alert
end
def self.object_sym
:alert
end
def parent_records(parent, ids)
parent.alert_management_alerts.where(iid: ids.to_a)
end
def parent_records(parent, ids)
parent.alert_management_alerts.where(iid: ids.to_a)
end
def url_for_object(alert, project)
::Gitlab::Routing.url_helpers.details_project_alert_management_url(
project,
alert.iid,
only_path: context[:only_path]
)
def url_for_object(alert, project)
::Gitlab::Routing.url_helpers.details_project_alert_management_url(
project,
alert.iid,
only_path: context[:only_path]
)
end
end
end
end
......
......@@ -2,44 +2,46 @@
module Banzai
module Filter
# HTML filter that replaces commit range references with links.
#
# This filter supports cross-project references.
class CommitRangeReferenceFilter < AbstractReferenceFilter
self.reference_type = :commit_range
def self.object_class
CommitRange
end
module References
# HTML filter that replaces commit range references with links.
#
# This filter supports cross-project references.
class CommitRangeReferenceFilter < AbstractReferenceFilter
self.reference_type = :commit_range
def self.object_class
CommitRange
end
def self.references_in(text, pattern = CommitRange.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit_range], $~[:project], $~[:namespace], $~
def self.references_in(text, pattern = CommitRange.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit_range], $~[:project], $~[:namespace], $~
end
end
end
def initialize(*args)
super
def initialize(*args)
super
@commit_map = {}
end
@commit_map = {}
end
def find_object(project, id)
return unless project.is_a?(Project)
def find_object(project, id)
return unless project.is_a?(Project)
range = CommitRange.new(id, project)
range = CommitRange.new(id, project)
range.valid_commits? ? range : nil
end
range.valid_commits? ? range : nil
end
def url_for_object(range, project)
h = Gitlab::Routing.url_helpers
h.project_compare_url(project,
range.to_param.merge(only_path: context[:only_path]))
end
def url_for_object(range, project)
h = Gitlab::Routing.url_helpers
h.project_compare_url(project,
range.to_param.merge(only_path: context[:only_path]))
end
def object_link_title(range, matches)
nil
def object_link_title(range, matches)
nil
end
end
end
end
......
......@@ -2,84 +2,86 @@
module Banzai
module Filter
# HTML filter that replaces commit references with links.
#
# This filter supports cross-project references.
class CommitReferenceFilter < AbstractReferenceFilter
self.reference_type = :commit
def self.object_class
Commit
end
module References
# HTML filter that replaces commit references with links.
#
# This filter supports cross-project references.
class CommitReferenceFilter < AbstractReferenceFilter
self.reference_type = :commit
def self.object_class
Commit
end
def self.references_in(text, pattern = Commit.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit], $~[:project], $~[:namespace], $~
def self.references_in(text, pattern = Commit.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit], $~[:project], $~[:namespace], $~
end
end
end
def find_object(project, id)
return unless project.is_a?(Project) && project.valid_repo?
def find_object(project, id)
return unless project.is_a?(Project) && project.valid_repo?
_, record = records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) }
_, record = records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) }
record
end
record
end
def referenced_merge_request_commit_shas
return [] unless noteable.is_a?(MergeRequest)
def referenced_merge_request_commit_shas
return [] unless noteable.is_a?(MergeRequest)
@referenced_merge_request_commit_shas ||= begin
referenced_shas = references_per_parent.values.reduce(:|).to_a
noteable.all_commit_shas.select do |sha|
referenced_shas.any? { |ref| Gitlab::Git.shas_eql?(sha, ref) }
@referenced_merge_request_commit_shas ||= begin
referenced_shas = references_per_parent.values.reduce(:|).to_a
noteable.all_commit_shas.select do |sha|
referenced_shas.any? { |ref| Gitlab::Git.shas_eql?(sha, ref) }
end
end
end
end
# The default behaviour is `#to_i` - we just pass the hash through.
def self.parse_symbol(sha_hash, _match)
sha_hash
end
# The default behaviour is `#to_i` - we just pass the hash through.
def self.parse_symbol(sha_hash, _match)
sha_hash
end
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
if referenced_merge_request_commit_shas.include?(commit.id)
h.diffs_project_merge_request_url(project,
noteable,
commit_id: commit.id,
only_path: only_path?)
else
h.project_commit_url(project,
commit,
only_path: only_path?)
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
if referenced_merge_request_commit_shas.include?(commit.id)
h.diffs_project_merge_request_url(project,
noteable,
commit_id: commit.id,
only_path: only_path?)
else
h.project_commit_url(project,
commit,
only_path: only_path?)
end
end
end
def object_link_text_extras(object, matches)
extras = super
def object_link_text_extras(object, matches)
extras = super
path = matches[:path] if matches.names.include?("path")
if path == '/builds'
extras.unshift "builds"
end
path = matches[:path] if matches.names.include?("path")
if path == '/builds'
extras.unshift "builds"
end
extras
end
extras
end
private
private
def parent_records(parent, ids)
parent.commits_by(oids: ids.to_a)
end
def parent_records(parent, ids)
parent.commits_by(oids: ids.to_a)
end
def noteable
context[:noteable]
end
def noteable
context[:noteable]
end
def only_path?
context[:only_path]
def only_path?
context[:only_path]
end
end
end
end
......
......@@ -2,105 +2,107 @@
module Banzai
module Filter
class DesignReferenceFilter < AbstractReferenceFilter
class Identifier
include Comparable
attr_reader :issue_iid, :filename
def initialize(issue_iid:, filename:)
@issue_iid = issue_iid
@filename = filename
module References
class DesignReferenceFilter < AbstractReferenceFilter
class Identifier
include Comparable
attr_reader :issue_iid, :filename
def initialize(issue_iid:, filename:)
@issue_iid = issue_iid
@filename = filename
end
def as_composite_id(id_for_iid)
id = id_for_iid[issue_iid]
return unless id
{ issue_id: id, filename: filename }
end
def <=>(other)
return unless other.is_a?(Identifier)
[issue_iid, filename] <=> [other.issue_iid, other.filename]
end
alias_method :eql?, :==
def hash
[issue_iid, filename].hash
end
end
def as_composite_id(id_for_iid)
id = id_for_iid[issue_iid]
return unless id
self.reference_type = :design
{ issue_id: id, filename: filename }
def find_object(project, identifier)
records_per_parent[project][identifier]
end
def <=>(other)
return unless other.is_a?(Identifier)
def parent_records(project, identifiers)
return [] unless project.design_management_enabled?
[issue_iid, filename] <=> [other.issue_iid, other.filename]
end
alias_method :eql?, :==
iids = identifiers.map(&:issue_iid).to_set
issues = project.issues.where(iid: iids)
id_for_iid = issues.index_by(&:iid).transform_values(&:id)
issue_by_id = issues.index_by(&:id)
def hash
[issue_iid, filename].hash
designs(identifiers, id_for_iid).each do |d|
issue = issue_by_id[d.issue_id]
# optimisation: assign values we have already fetched
d.project = project
d.issue = issue
end
end
end
self.reference_type = :design
def find_object(project, identifier)
records_per_parent[project][identifier]
end
def parent_records(project, identifiers)
return [] unless project.design_management_enabled?
iids = identifiers.map(&:issue_iid).to_set
issues = project.issues.where(iid: iids)
id_for_iid = issues.index_by(&:iid).transform_values(&:id)
issue_by_id = issues.index_by(&:id)
designs(identifiers, id_for_iid).each do |d|
issue = issue_by_id[d.issue_id]
# optimisation: assign values we have already fetched
d.project = project
d.issue = issue
def relation_for_paths(paths)
super.includes(:route, :namespace, :group)
end
end
def relation_for_paths(paths)
super.includes(:route, :namespace, :group)
end
def parent_type
:project
end
def parent_type
:project
end
# optimisation to reuse the parent_per_reference query information
def parent_from_ref(ref)
parent_per_reference[ref || current_parent_path]
end
# optimisation to reuse the parent_per_reference query information
def parent_from_ref(ref)
parent_per_reference[ref || current_parent_path]
end
def url_for_object(design, project)
path_options = { vueroute: design.filename }
Gitlab::Routing.url_helpers.designs_project_issue_path(project, design.issue, path_options)
end
def url_for_object(design, project)
path_options = { vueroute: design.filename }
Gitlab::Routing.url_helpers.designs_project_issue_path(project, design.issue, path_options)
end
def data_attributes_for(_text, _project, design, **_kwargs)
super.merge(issue: design.issue_id)
end
def data_attributes_for(_text, _project, design, **_kwargs)
super.merge(issue: design.issue_id)
end
def self.object_class
::DesignManagement::Design
end
def self.object_class
::DesignManagement::Design
end
def self.object_sym
:design
end
def self.object_sym
:design
end
def self.parse_symbol(raw, match_data)
filename = match_data[:url_filename]
iid = match_data[:issue].to_i
Identifier.new(filename: CGI.unescape(filename), issue_iid: iid)
end
def self.parse_symbol(raw, match_data)
filename = match_data[:url_filename]
iid = match_data[:issue].to_i
Identifier.new(filename: CGI.unescape(filename), issue_iid: iid)
end
def record_identifier(design)
Identifier.new(filename: design.filename, issue_iid: design.issue.iid)
end
def record_identifier(design)
Identifier.new(filename: design.filename, issue_iid: design.issue.iid)
end
private
private
def designs(identifiers, id_for_iid)
identifiers
.map { |identifier| identifier.as_composite_id(id_for_iid) }
.compact
.in_groups_of(100, false) # limitation of by_issue_id_and_filename, so we batch
.flat_map { |ids| DesignManagement::Design.by_issue_id_and_filename(ids) }
def designs(identifiers, id_for_iid)
identifiers
.map { |identifier| identifier.as_composite_id(id_for_iid) }
.compact
.in_groups_of(100, false) # limitation of by_issue_id_and_filename, so we batch
.flat_map { |ids| DesignManagement::Design.by_issue_id_and_filename(ids) }
end
end
end
end
......
......@@ -2,21 +2,23 @@
module Banzai
module Filter
# The actual filter is implemented in the EE mixin
class EpicReferenceFilter < IssuableReferenceFilter
self.reference_type = :epic
module References
# The actual filter is implemented in the EE mixin
class EpicReferenceFilter < IssuableReferenceFilter
self.reference_type = :epic
def self.object_class
Epic
end
def self.object_class
Epic
end
private
private
def group
context[:group] || context[:project]&.group
def group
context[:group] || context[:project]&.group
end
end
end
end
end
Banzai::Filter::EpicReferenceFilter.prepend_if_ee('EE::Banzai::Filter::EpicReferenceFilter')
Banzai::Filter::References::EpicReferenceFilter.prepend_if_ee('EE::Banzai::Filter::References::EpicReferenceFilter')
......@@ -2,116 +2,118 @@
module Banzai
module Filter
# HTML filter that replaces external issue tracker references with links.
# References are ignored if the project doesn't use an external issue
# tracker.
#
# This filter does not support cross-project references.
class ExternalIssueReferenceFilter < ReferenceFilter
self.reference_type = :external_issue
# Public: Find `JIRA-123` issue references in text
module References
# HTML filter that replaces external issue tracker references with links.
# References are ignored if the project doesn't use an external issue
# tracker.
#
# ExternalIssueReferenceFilter.references_in(text, pattern) do |match, issue|
# "<a href=...>##{issue}</a>"
# end
#
# text - String text to search.
#
# Yields the String match and the String issue reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text, pattern)
text.gsub(pattern) do |match|
yield match, $~[:issue]
# This filter does not support cross-project references.
class ExternalIssueReferenceFilter < ReferenceFilter
self.reference_type = :external_issue
# Public: Find `JIRA-123` issue references in text
#
# ExternalIssueReferenceFilter.references_in(text, pattern) do |match, issue|
# "<a href=...>##{issue}</a>"
# end
#
# text - String text to search.
#
# Yields the String match and the String issue reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text, pattern)
text.gsub(pattern) do |match|
yield match, $~[:issue]
end
end
end
def call
# Early return if the project isn't using an external tracker
return doc if project.nil? || default_issues_tracker?
def call
# Early return if the project isn't using an external tracker
return doc if project.nil? || default_issues_tracker?
ref_pattern = issue_reference_pattern
ref_start_pattern = /\A#{ref_pattern}\z/
ref_pattern = issue_reference_pattern
ref_start_pattern = /\A#{ref_pattern}\z/
nodes.each_with_index do |node, index|
if text_node?(node)
replace_text_when_pattern_matches(node, index, ref_pattern) do |content|
issue_link_filter(content)
end
nodes.each_with_index do |node, index|
if text_node?(node)
replace_text_when_pattern_matches(node, index, ref_pattern) do |content|
issue_link_filter(content)
end
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if link =~ ref_start_pattern
replace_link_node_with_href(node, index, link) do
issue_link_filter(link, link_content: inner_html)
elsif element_node?(node)
yield_valid_link(node) do |link, inner_html|
if link =~ ref_start_pattern
replace_link_node_with_href(node, index, link) do
issue_link_filter(link, link_content: inner_html)
end
end
end
end
end
end
doc
end
private
doc
end
# Replace `JIRA-123` issue references in text with links to the referenced
# issue's details page.
#
# text - String text to replace references in.
# link_content - Original content of the link being replaced.
#
# Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text, link_content: nil)
self.class.references_in(text, issue_reference_pattern) do |match, id|
url = url_for_issue(id)
klass = reference_class(:issue)
data = data_attribute(project: project.id, external_issue: id)
content = link_content || match
%(<a href="#{url}" #{data}
title="#{escape_once(issue_title)}"
class="#{klass}">#{content}</a>)
private
# Replace `JIRA-123` issue references in text with links to the referenced
# issue's details page.
#
# text - String text to replace references in.
# link_content - Original content of the link being replaced.
#
# Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text, link_content: nil)
self.class.references_in(text, issue_reference_pattern) do |match, id|
url = url_for_issue(id)
klass = reference_class(:issue)
data = data_attribute(project: project.id, external_issue: id)
content = link_content || match
%(<a href="#{url}" #{data}
title="#{escape_once(issue_title)}"
class="#{klass}">#{content}</a>)
end
end
end
def url_for_issue(issue_id)
return '' if project.nil?
def url_for_issue(issue_id)
return '' if project.nil?
url = if only_path?
project.external_issue_tracker.issue_path(issue_id)
else
project.external_issue_tracker.issue_url(issue_id)
end
url = if only_path?
project.external_issue_tracker.issue_path(issue_id)
else
project.external_issue_tracker.issue_url(issue_id)
end
# Ensure we return a valid URL to prevent possible XSS.
URI.parse(url).to_s
rescue URI::InvalidURIError
''
end
# Ensure we return a valid URL to prevent possible XSS.
URI.parse(url).to_s
rescue URI::InvalidURIError
''
end
def default_issues_tracker?
external_issues_cached(:default_issues_tracker?)
end
def default_issues_tracker?
external_issues_cached(:default_issues_tracker?)
end
def issue_reference_pattern
external_issues_cached(:external_issue_reference_pattern)
end
def issue_reference_pattern
external_issues_cached(:external_issue_reference_pattern)
end
def project
context[:project]
end
def project
context[:project]
end
def issue_title
"Issue in #{project.external_issue_tracker.title}"
end
def issue_title
"Issue in #{project.external_issue_tracker.title}"
end
def external_issues_cached(attribute)
cached_attributes = Gitlab::SafeRequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} }
cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? # rubocop:disable GitlabSecurity/PublicSend
cached_attributes[project.id][attribute]
def external_issues_cached(attribute)
cached_attributes = Gitlab::SafeRequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} }
cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? # rubocop:disable GitlabSecurity/PublicSend
cached_attributes[project.id][attribute]
end
end
end
end
......
......@@ -2,31 +2,33 @@
module Banzai
module Filter
class FeatureFlagReferenceFilter < IssuableReferenceFilter
self.reference_type = :feature_flag
module References
class FeatureFlagReferenceFilter < IssuableReferenceFilter
self.reference_type = :feature_flag
def self.object_class
Operations::FeatureFlag
end
def self.object_class
Operations::FeatureFlag
end
def self.object_sym
:feature_flag
end
def self.object_sym
:feature_flag
end
def parent_records(parent, ids)
parent.operations_feature_flags.where(iid: ids.to_a)
end
def parent_records(parent, ids)
parent.operations_feature_flags.where(iid: ids.to_a)
end
def url_for_object(feature_flag, project)
::Gitlab::Routing.url_helpers.edit_project_feature_flag_url(
project,
feature_flag.iid,
only_path: context[:only_path]
)
end
def url_for_object(feature_flag, project)
::Gitlab::Routing.url_helpers.edit_project_feature_flag_url(
project,
feature_flag.iid,
only_path: context[:only_path]
)
end
def object_link_title(object, matches)
object.name
def object_link_title(object, matches)
object.name
end
end
end
end
......
......@@ -2,17 +2,19 @@
module Banzai
module Filter
class IssuableReferenceFilter < AbstractReferenceFilter
def record_identifier(record)
record.iid.to_i
end
module References
class IssuableReferenceFilter < AbstractReferenceFilter
def record_identifier(record)
record.iid.to_i
end
def find_object(parent, iid)
records_per_parent[parent][iid]
end
def find_object(parent, iid)
records_per_parent[parent][iid]
end
def parent_from_ref(ref)
parent_per_reference[ref || current_parent_path]
def parent_from_ref(ref)
parent_per_reference[ref || current_parent_path]
end
end
end
end
......
......@@ -2,55 +2,57 @@
module Banzai
module Filter
# HTML filter that replaces issue references with links. References to
# issues that do not exist are ignored.
#
# This filter supports cross-project references.
#
# When external issues tracker like Jira is activated we should not
# use issue reference pattern, but we should still be able
# to reference issues from other GitLab projects.
class IssueReferenceFilter < IssuableReferenceFilter
self.reference_type = :issue
def self.object_class
Issue
end
module References
# HTML filter that replaces issue references with links. References to
# issues that do not exist are ignored.
#
# This filter supports cross-project references.
#
# When external issues tracker like Jira is activated we should not
# use issue reference pattern, but we should still be able
# to reference issues from other GitLab projects.
class IssueReferenceFilter < IssuableReferenceFilter
self.reference_type = :issue
def self.object_class
Issue
end
def url_for_object(issue, project)
return issue_path(issue, project) if only_path?
def url_for_object(issue, project)
return issue_path(issue, project) if only_path?
issue_url(issue, project)
end
issue_url(issue, project)
end
def parent_records(parent, ids)
parent.issues.where(iid: ids.to_a)
end
def parent_records(parent, ids)
parent.issues.where(iid: ids.to_a)
end
def object_link_text_extras(issue, matches)
super + design_link_extras(issue, matches.named_captures['path'])
end
def object_link_text_extras(issue, matches)
super + design_link_extras(issue, matches.named_captures['path'])
end
private
private
def issue_path(issue, project)
Gitlab::Routing.url_helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue.iid)
end
def issue_path(issue, project)
Gitlab::Routing.url_helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue.iid)
end
def issue_url(issue, project)
Gitlab::Routing.url_helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue.iid)
end
def issue_url(issue, project)
Gitlab::Routing.url_helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue.iid)
end
def design_link_extras(issue, path)
if path == '/designs' && read_designs?(issue)
['designs']
else
[]
def design_link_extras(issue, path)
if path == '/designs' && read_designs?(issue)
['designs']
else
[]
end
end
end
def read_designs?(issue)
issue.project.design_management_enabled?
def read_designs?(issue)
issue.project.design_management_enabled?
end
end
end
end
......
......@@ -2,15 +2,17 @@
module Banzai
module Filter
# The actual filter is implemented in the EE mixin
class IterationReferenceFilter < AbstractReferenceFilter
self.reference_type = :iteration
module References
# The actual filter is implemented in the EE mixin
class IterationReferenceFilter < AbstractReferenceFilter
self.reference_type = :iteration
def self.object_class
Iteration
def self.object_class
Iteration
end
end
end
end
end
Banzai::Filter::IterationReferenceFilter.prepend_if_ee('EE::Banzai::Filter::IterationReferenceFilter')
Banzai::Filter::References::IterationReferenceFilter.prepend_if_ee('EE::Banzai::Filter::References::IterationReferenceFilter')
......@@ -2,128 +2,130 @@
module Banzai
module Filter
# HTML filter that replaces label references with links.
class LabelReferenceFilter < AbstractReferenceFilter
self.reference_type = :label
module References
# HTML filter that replaces label references with links.
class LabelReferenceFilter < AbstractReferenceFilter
self.reference_type = :label
def self.object_class
Label
end
def find_object(parent_object, id)
find_labels(parent_object).find(id)
end
def references_in(text, pattern = Label.reference_pattern)
labels = {}
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
namespace, project = $~[:namespace], $~[:project]
project_path = full_project_path(namespace, project)
label = find_label_cached(project_path, $~[:label_id], $~[:label_name])
def self.object_class
Label
end
if label
labels[label.id] = yield match, label.id, project, namespace, $~
"#{REFERENCE_PLACEHOLDER}#{label.id}"
else
match
end
def find_object(parent_object, id)
find_labels(parent_object).find(id)
end
return text if labels.empty?
def references_in(text, pattern = Label.reference_pattern)
labels = {}
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
namespace, project = $~[:namespace], $~[:project]
project_path = full_project_path(namespace, project)
label = find_label_cached(project_path, $~[:label_id], $~[:label_name])
if label
labels[label.id] = yield match, label.id, project, namespace, $~
"#{REFERENCE_PLACEHOLDER}#{label.id}"
else
match
end
end
escape_with_placeholders(unescaped_html, labels)
end
return text if labels.empty?
def find_label_cached(parent_ref, label_id, label_name)
cached_call(:banzai_find_label_cached, label_name&.tr('"', '') || label_id, path: [object_class, parent_ref]) do
find_label(parent_ref, label_id, label_name)
escape_with_placeholders(unescaped_html, labels)
end
end
def find_label(parent_ref, label_id, label_name)
parent = parent_from_ref(parent_ref)
return unless parent
def find_label_cached(parent_ref, label_id, label_name)
cached_call(:banzai_find_label_cached, label_name&.tr('"', '') || label_id, path: [object_class, parent_ref]) do
find_label(parent_ref, label_id, label_name)
end
end
label_params = label_params(label_id, label_name)
find_labels(parent).find_by(label_params)
end
def find_label(parent_ref, label_id, label_name)
parent = parent_from_ref(parent_ref)
return unless parent
def find_labels(parent)
params = if parent.is_a?(Group)
{ group_id: parent.id,
include_ancestor_groups: true,
only_group_labels: true }
else
{ project: parent,
include_ancestor_groups: true }
end
LabelsFinder.new(nil, params).execute(skip_authorization: true)
end
label_params = label_params(label_id, label_name)
find_labels(parent).find_by(label_params)
end
# Parameters to pass to `Label.find_by` based on the given arguments
#
# id - Integer ID to pass. If present, returns {id: id}
# name - String name to pass. If `id` is absent, finds by name without
# surrounding quotes.
#
# Returns a Hash.
def label_params(id, name)
if name
{ name: name.tr('"', '') }
else
{ id: id.to_i }
def find_labels(parent)
params = if parent.is_a?(Group)
{ group_id: parent.id,
include_ancestor_groups: true,
only_group_labels: true }
else
{ project: parent,
include_ancestor_groups: true }
end
LabelsFinder.new(nil, params).execute(skip_authorization: true)
end
end
def url_for_object(label, parent)
label_url_method =
if context[:label_url_method]
context[:label_url_method]
elsif parent.is_a?(Project)
:project_issues_url
# Parameters to pass to `Label.find_by` based on the given arguments
#
# id - Integer ID to pass. If present, returns {id: id}
# name - String name to pass. If `id` is absent, finds by name without
# surrounding quotes.
#
# Returns a Hash.
def label_params(id, name)
if name
{ name: name.tr('"', '') }
else
{ id: id.to_i }
end
end
return unless label_url_method
def url_for_object(label, parent)
label_url_method =
if context[:label_url_method]
context[:label_url_method]
elsif parent.is_a?(Project)
:project_issues_url
end
Gitlab::Routing.url_helpers.public_send(label_url_method, parent, label_name: label.name, only_path: context[:only_path]) # rubocop:disable GitlabSecurity/PublicSend
end
return unless label_url_method
def object_link_text(object, matches)
label_suffix = ''
parent = project || group
Gitlab::Routing.url_helpers.public_send(label_url_method, parent, label_name: label.name, only_path: context[:only_path]) # rubocop:disable GitlabSecurity/PublicSend
end
if project || full_path_ref?(matches)
project_path = full_project_path(matches[:namespace], matches[:project])
parent_from_ref = from_ref_cached(project_path)
reference = parent_from_ref.to_human_reference(parent)
def object_link_text(object, matches)
label_suffix = ''
parent = project || group
label_suffix = " <i>in #{ERB::Util.html_escape(reference)}</i>" if reference.present?
end
if project || full_path_ref?(matches)
project_path = full_project_path(matches[:namespace], matches[:project])
parent_from_ref = from_ref_cached(project_path)
reference = parent_from_ref.to_human_reference(parent)
presenter = object.present(issuable_subject: parent)
LabelsHelper.render_colored_label(presenter, suffix: label_suffix)
end
label_suffix = " <i>in #{ERB::Util.html_escape(reference)}</i>" if reference.present?
end
def wrap_link(link, label)
presenter = label.present(issuable_subject: project || group)
LabelsHelper.wrap_label_html(link, small: true, label: presenter)
end
presenter = object.present(issuable_subject: parent)
LabelsHelper.render_colored_label(presenter, suffix: label_suffix)
end
def full_path_ref?(matches)
matches[:namespace] && matches[:project]
end
def wrap_link(link, label)
presenter = label.present(issuable_subject: project || group)
LabelsHelper.wrap_label_html(link, small: true, label: presenter)
end
def reference_class(type, tooltip: true)
super + ' gl-link gl-label-link'
end
def full_path_ref?(matches)
matches[:namespace] && matches[:project]
end
def reference_class(type, tooltip: true)
super + ' gl-link gl-label-link'
end
def object_link_title(object, matches)
presenter = object.present(issuable_subject: project || group)
LabelsHelper.label_tooltip_title(presenter)
def object_link_title(object, matches)
presenter = object.present(issuable_subject: project || group)
LabelsHelper.label_tooltip_title(presenter)
end
end
end
end
end
Banzai::Filter::LabelReferenceFilter.prepend_if_ee('EE::Banzai::Filter::LabelReferenceFilter')
Banzai::Filter::References::LabelReferenceFilter.prepend_if_ee('EE::Banzai::Filter::References::LabelReferenceFilter')
......@@ -2,95 +2,97 @@
module Banzai
module Filter
# HTML filter that replaces merge request references with links. References
# to merge requests that do not exist are ignored.
#
# This filter supports cross-project references.
class MergeRequestReferenceFilter < IssuableReferenceFilter
self.reference_type = :merge_request
def self.object_class
MergeRequest
end
module References
# HTML filter that replaces merge request references with links. References
# to merge requests that do not exist are ignored.
#
# This filter supports cross-project references.
class MergeRequestReferenceFilter < IssuableReferenceFilter
self.reference_type = :merge_request
def self.object_class
MergeRequest
end
def url_for_object(mr, project)
h = Gitlab::Routing.url_helpers
h.project_merge_request_url(project, mr,
only_path: context[:only_path])
end
def url_for_object(mr, project)
h = Gitlab::Routing.url_helpers
h.project_merge_request_url(project, mr,
only_path: context[:only_path])
end
def object_link_title(object, matches)
# The method will return `nil` if object is not a commit
# allowing for properly handling the extended MR Tooltip
object_link_commit_title(object, matches)
end
def object_link_title(object, matches)
# The method will return `nil` if object is not a commit
# allowing for properly handling the extended MR Tooltip
object_link_commit_title(object, matches)
end
def object_link_text_extras(object, matches)
extras = super
def object_link_text_extras(object, matches)
extras = super
if commit_ref = object_link_commit_ref(object, matches)
klass = reference_class(:commit, tooltip: false)
commit_ref_tag = %(<span class="#{klass}">#{commit_ref}</span>)
if commit_ref = object_link_commit_ref(object, matches)
klass = reference_class(:commit, tooltip: false)
commit_ref_tag = %(<span class="#{klass}">#{commit_ref}</span>)
return extras.unshift(commit_ref_tag)
end
return extras.unshift(commit_ref_tag)
end
path = matches[:path] if matches.names.include?("path")
path = matches[:path] if matches.names.include?("path")
case path
when '/diffs'
extras.unshift "diffs"
when '/commits'
extras.unshift "commits"
when '/builds'
extras.unshift "builds"
end
case path
when '/diffs'
extras.unshift "diffs"
when '/commits'
extras.unshift "commits"
when '/builds'
extras.unshift "builds"
end
extras
end
extras
end
def parent_records(parent, ids)
parent.merge_requests
.where(iid: ids.to_a)
.includes(target_project: :namespace)
end
def parent_records(parent, ids)
parent.merge_requests
.where(iid: ids.to_a)
.includes(target_project: :namespace)
end
def reference_class(object_sym, options = {})
super(object_sym, tooltip: false)
end
def reference_class(object_sym, options = {})
super(object_sym, tooltip: false)
end
def data_attributes_for(text, parent, object, **data)
super.merge(project_path: parent.full_path, iid: object.iid, mr_title: object.title)
end
def data_attributes_for(text, parent, object, **data)
super.merge(project_path: parent.full_path, iid: object.iid, mr_title: object.title)
end
private
private
def object_link_commit_title(object, matches)
object_link_commit(object, matches)&.title
end
def object_link_commit_title(object, matches)
object_link_commit(object, matches)&.title
end
def object_link_commit_ref(object, matches)
object_link_commit(object, matches)&.short_id
end
def object_link_commit_ref(object, matches)
object_link_commit(object, matches)&.short_id
end
def object_link_commit(object, matches)
return unless matches.names.include?('query') && query = matches[:query]
def object_link_commit(object, matches)
return unless matches.names.include?('query') && query = matches[:query]
# Removes leading "?". CGI.parse expects "arg1&arg2&arg3"
params = CGI.parse(query.sub(/^\?/, ''))
# Removes leading "?". CGI.parse expects "arg1&arg2&arg3"
params = CGI.parse(query.sub(/^\?/, ''))
return unless commit_sha = params['commit_id']&.first
return unless commit_sha = params['commit_id']&.first
if commit = find_commit_by_sha(object, commit_sha)
Commit.from_hash(commit.to_hash, object.project)
if commit = find_commit_by_sha(object, commit_sha)
Commit.from_hash(commit.to_hash, object.project)
end
end
end
def find_commit_by_sha(object, commit_sha)
@all_commits ||= {}
@all_commits[object.id] ||= object.all_commits
def find_commit_by_sha(object, commit_sha)
@all_commits ||= {}
@all_commits[object.id] ||= object.all_commits
@all_commits[object.id].find { |commit| commit.sha == commit_sha }
@all_commits[object.id].find { |commit| commit.sha == commit_sha }
end
end
end
end
......
......@@ -2,136 +2,138 @@
module Banzai
module Filter
# HTML filter that replaces milestone references with links.
class MilestoneReferenceFilter < AbstractReferenceFilter
include Gitlab::Utils::StrongMemoize
module References
# HTML filter that replaces milestone references with links.
class MilestoneReferenceFilter < AbstractReferenceFilter
include Gitlab::Utils::StrongMemoize
self.reference_type = :milestone
self.reference_type = :milestone
def self.object_class
Milestone
end
def self.object_class
Milestone
end
# Links to project milestones contain the IID, but when we're handling
# 'regular' references, we need to use the global ID to disambiguate
# between group and project milestones.
def find_object(parent, id)
return unless valid_context?(parent)
# Links to project milestones contain the IID, but when we're handling
# 'regular' references, we need to use the global ID to disambiguate
# between group and project milestones.
def find_object(parent, id)
return unless valid_context?(parent)
find_milestone_with_finder(parent, id: id)
end
find_milestone_with_finder(parent, id: id)
end
def find_object_from_link(parent, iid)
return unless valid_context?(parent)
def find_object_from_link(parent, iid)
return unless valid_context?(parent)
find_milestone_with_finder(parent, iid: iid)
end
def valid_context?(parent)
strong_memoize(:valid_context) do
group_context?(parent) || project_context?(parent)
find_milestone_with_finder(parent, iid: iid)
end
end
def group_context?(parent)
strong_memoize(:group_context) do
parent.is_a?(Group)
def valid_context?(parent)
strong_memoize(:valid_context) do
group_context?(parent) || project_context?(parent)
end
end
end
def project_context?(parent)
strong_memoize(:project_context) do
parent.is_a?(Project)
def group_context?(parent)
strong_memoize(:group_context) do
parent.is_a?(Group)
end
end
end
def references_in(text, pattern = Milestone.reference_pattern)
# We'll handle here the references that follow the `reference_pattern`.
# Other patterns (for example, the link pattern) are handled by the
# default implementation.
return super(text, pattern) if pattern != Milestone.reference_pattern
milestones = {}
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~
"#{REFERENCE_PLACEHOLDER}#{milestone.id}"
else
match
def project_context?(parent)
strong_memoize(:project_context) do
parent.is_a?(Project)
end
end
return text if milestones.empty?
def references_in(text, pattern = Milestone.reference_pattern)
# We'll handle here the references that follow the `reference_pattern`.
# Other patterns (for example, the link pattern) are handled by the
# default implementation.
return super(text, pattern) if pattern != Milestone.reference_pattern
milestones = {}
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~
"#{REFERENCE_PLACEHOLDER}#{milestone.id}"
else
match
end
end
escape_with_placeholders(unescaped_html, milestones)
end
return text if milestones.empty?
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
project_path = full_project_path(namespace_ref, project_ref)
escape_with_placeholders(unescaped_html, milestones)
end
# Returns group if project is not found by path
parent = parent_from_ref(project_path)
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
project_path = full_project_path(namespace_ref, project_ref)
return unless parent
# Returns group if project is not found by path
parent = parent_from_ref(project_path)
milestone_params = milestone_params(milestone_id, milestone_name)
return unless parent
find_milestone_with_finder(parent, milestone_params)
end
milestone_params = milestone_params(milestone_id, milestone_name)
def milestone_params(iid, name)
if name
{ name: name.tr('"', '') }
else
{ iid: iid.to_i }
find_milestone_with_finder(parent, milestone_params)
end
end
def find_milestone_with_finder(parent, params)
finder_params = milestone_finder_params(parent, params[:iid].present?)
def milestone_params(iid, name)
if name
{ name: name.tr('"', '') }
else
{ iid: iid.to_i }
end
end
MilestonesFinder.new(finder_params).find_by(params)
end
def find_milestone_with_finder(parent, params)
finder_params = milestone_finder_params(parent, params[:iid].present?)
def milestone_finder_params(parent, find_by_iid)
{ order: nil, state: 'all' }.tap do |params|
params[:project_ids] = parent.id if project_context?(parent)
MilestonesFinder.new(finder_params).find_by(params)
end
def milestone_finder_params(parent, find_by_iid)
{ order: nil, state: 'all' }.tap do |params|
params[:project_ids] = parent.id if project_context?(parent)
# We don't support IID lookups because IIDs can clash between
# group/project milestones and group/subgroup milestones.
params[:group_ids] = self_and_ancestors_ids(parent) unless find_by_iid
# We don't support IID lookups because IIDs can clash between
# group/project milestones and group/subgroup milestones.
params[:group_ids] = self_and_ancestors_ids(parent) unless find_by_iid
end
end
end
def self_and_ancestors_ids(parent)
if group_context?(parent)
parent.self_and_ancestors.select(:id)
elsif project_context?(parent)
parent.group&.self_and_ancestors&.select(:id)
def self_and_ancestors_ids(parent)
if group_context?(parent)
parent.self_and_ancestors.select(:id)
elsif project_context?(parent)
parent.group&.self_and_ancestors&.select(:id)
end
end
end
def url_for_object(milestone, project)
Gitlab::Routing
.url_helpers
.milestone_url(milestone, only_path: context[:only_path])
end
def url_for_object(milestone, project)
Gitlab::Routing
.url_helpers
.milestone_url(milestone, only_path: context[:only_path])
end
def object_link_text(object, matches)
milestone_link = escape_once(super)
reference = object.project&.to_reference_base(project)
def object_link_text(object, matches)
milestone_link = escape_once(super)
reference = object.project&.to_reference_base(project)
if reference.present?
"#{milestone_link} <i>in #{reference}</i>".html_safe
else
milestone_link
if reference.present?
"#{milestone_link} <i>in #{reference}</i>".html_safe
else
milestone_link
end
end
end
def object_link_title(object, matches)
nil
def object_link_title(object, matches)
nil
end
end
end
end
......
......@@ -2,115 +2,117 @@
module Banzai
module Filter
# HTML filter that replaces project references with links.
class ProjectReferenceFilter < ReferenceFilter
self.reference_type = :project
# Public: Find `namespace/project>` project references in text
#
# ProjectReferenceFilter.references_in(text) do |match, project|
# "<a href=...>#{project}></a>"
# 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]}"
module References
# HTML filter that replaces project references with links.
class ProjectReferenceFilter < ReferenceFilter
self.reference_type = :project
# Public: Find `namespace/project>` project references in text
#
# ProjectReferenceFilter.references_in(text) do |match, project|
# "<a href=...>#{project}></a>"
# 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
end
def call
ref_pattern = Project.markdown_reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
def call
ref_pattern = Project.markdown_reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
nodes.each_with_index do |node, index|
if text_node?(node)
replace_text_when_pattern_matches(node, index, 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, index, link) do
project_link_filter(link, link_content: inner_html)
nodes.each_with_index do |node, index|
if text_node?(node)
replace_text_when_pattern_matches(node, index, 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, index, link) do
project_link_filter(link, link_content: inner_html)
end
end
end
end
end
end
doc
end
doc
end
# 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 `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|
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
# 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 `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|
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
end
end
end
end
end
# Returns a Hash containing all Project objects for the project
# references in the current document.
#
# The keys of this Hash are the project paths, the values the
# corresponding Project objects.
def projects_hash
@projects ||= Project.eager_load(:route, namespace: [:route])
.where_full_path_in(projects)
.index_by(&:full_path)
.transform_keys(&:downcase)
end
# Returns a Hash containing all Project objects for the project
# references in the current document.
#
# The keys of this Hash are the project paths, the values the
# corresponding Project objects.
def projects_hash
@projects ||= Project.eager_load(:route, namespace: [:route])
.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
# 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]}"
nodes.each do |node|
node.to_html.scan(Project.markdown_reference_pattern) do
refs << "#{$~[:namespace]}/#{$~[:project]}"
end
end
end
refs.to_a
end
refs.to_a
end
private
private
def urls
Gitlab::Routing.url_helpers
end
def urls
Gitlab::Routing.url_helpers
end
def link_class
reference_class(:project)
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.to_reference
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.to_reference
link_tag(url, data, content, project.name)
end
link_tag(url, data, content, project.name)
end
def link_tag(url, data, link_content, title)
%(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>)
def link_tag(url, data, link_content, title)
%(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>)
end
end
end
end
......
......@@ -2,27 +2,29 @@
module Banzai
module Filter
# HTML filter that replaces snippet references with links. References to
# snippets that do not exist are ignored.
#
# This filter supports cross-project references.
class SnippetReferenceFilter < AbstractReferenceFilter
self.reference_type = :snippet
module References
# HTML filter that replaces snippet references with links. References to
# snippets that do not exist are ignored.
#
# This filter supports cross-project references.
class SnippetReferenceFilter < AbstractReferenceFilter
self.reference_type = :snippet
def self.object_class
Snippet
end
def self.object_class
Snippet
end
def find_object(project, id)
return unless project.is_a?(Project)
def find_object(project, id)
return unless project.is_a?(Project)
project.snippets.find_by(id: id)
end
project.snippets.find_by(id: id)
end
def url_for_object(snippet, project)
h = Gitlab::Routing.url_helpers
h.project_snippet_url(project, snippet,
only_path: context[:only_path])
def url_for_object(snippet, project)
h = Gitlab::Routing.url_helpers
h.project_snippet_url(project, snippet,
only_path: context[:only_path])
end
end
end
end
......
......@@ -2,21 +2,23 @@
module Banzai
module Filter
# The actual filter is implemented in the EE mixin
class VulnerabilityReferenceFilter < IssuableReferenceFilter
self.reference_type = :vulnerability
module References
# The actual filter is implemented in the EE mixin
class VulnerabilityReferenceFilter < IssuableReferenceFilter
self.reference_type = :vulnerability
def self.object_class
Vulnerability
end
def self.object_class
Vulnerability
end
private
private
def project
context[:project]
def project
context[:project]
end
end
end
end
end
Banzai::Filter::VulnerabilityReferenceFilter.prepend_if_ee('EE::Banzai::Filter::VulnerabilityReferenceFilter')
Banzai::Filter::References::VulnerabilityReferenceFilter.prepend_if_ee('EE::Banzai::Filter::References::VulnerabilityReferenceFilter')
......@@ -51,19 +51,19 @@ module Banzai
def self.reference_filters
[
Filter::UserReferenceFilter,
Filter::ProjectReferenceFilter,
Filter::DesignReferenceFilter,
Filter::IssueReferenceFilter,
Filter::ExternalIssueReferenceFilter,
Filter::MergeRequestReferenceFilter,
Filter::SnippetReferenceFilter,
Filter::CommitRangeReferenceFilter,
Filter::CommitReferenceFilter,
Filter::LabelReferenceFilter,
Filter::MilestoneReferenceFilter,
Filter::AlertReferenceFilter,
Filter::FeatureFlagReferenceFilter
Filter::References::UserReferenceFilter,
Filter::References::ProjectReferenceFilter,
Filter::References::DesignReferenceFilter,
Filter::References::IssueReferenceFilter,
Filter::References::ExternalIssueReferenceFilter,
Filter::References::MergeRequestReferenceFilter,
Filter::References::SnippetReferenceFilter,
Filter::References::CommitRangeReferenceFilter,
Filter::References::CommitReferenceFilter,
Filter::References::LabelReferenceFilter,
Filter::References::MilestoneReferenceFilter,
Filter::References::AlertReferenceFilter,
Filter::References::FeatureFlagReferenceFilter
]
end
......
......@@ -6,7 +6,7 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::SanitizationFilter,
Filter::LabelReferenceFilter
Filter::References::LabelReferenceFilter
]
end
end
......
......@@ -17,15 +17,15 @@ module Banzai
def self.reference_filters
[
Filter::UserReferenceFilter,
Filter::IssueReferenceFilter,
Filter::ExternalIssueReferenceFilter,
Filter::MergeRequestReferenceFilter,
Filter::SnippetReferenceFilter,
Filter::CommitRangeReferenceFilter,
Filter::CommitReferenceFilter,
Filter::AlertReferenceFilter,
Filter::FeatureFlagReferenceFilter
Filter::References::UserReferenceFilter,
Filter::References::IssueReferenceFilter,
Filter::References::ExternalIssueReferenceFilter,
Filter::References::MergeRequestReferenceFilter,
Filter::References::SnippetReferenceFilter,
Filter::References::CommitRangeReferenceFilter,
Filter::References::CommitReferenceFilter,
Filter::References::AlertReferenceFilter,
Filter::References::FeatureFlagReferenceFilter
]
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::AbstractReferenceFilter do
RSpec.describe Banzai::Filter::References::AbstractReferenceFilter do
let_it_be(:project) { create(:project) }
let(:doc) { Nokogiri::HTML.fragment('') }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::AlertReferenceFilter do
RSpec.describe Banzai::Filter::References::AlertReferenceFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project, :public) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::CommitRangeReferenceFilter do
RSpec.describe Banzai::Filter::References::CommitRangeReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::CommitReferenceFilter do
RSpec.describe Banzai::Filter::References::CommitReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::DesignReferenceFilter do
RSpec.describe Banzai::Filter::References::DesignReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::ExternalIssueReferenceFilter do
RSpec.describe Banzai::Filter::References::ExternalIssueReferenceFilter do
include FilterSpecHelper
let_it_be_with_refind(:project) { create(:project) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::FeatureFlagReferenceFilter do
RSpec.describe Banzai::Filter::References::FeatureFlagReferenceFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project, :public) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::IssueReferenceFilter do
RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
......
......@@ -3,7 +3,7 @@
require 'spec_helper'
require 'html/pipeline'
RSpec.describe Banzai::Filter::LabelReferenceFilter do
RSpec.describe Banzai::Filter::References::LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, name: 'sample-project') }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::MergeRequestReferenceFilter do
RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::MilestoneReferenceFilter do
RSpec.describe Banzai::Filter::References::MilestoneReferenceFilter do
include FilterSpecHelper
let_it_be(:parent_group) { create(:group, :public) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::ProjectReferenceFilter do
RSpec.describe Banzai::Filter::References::ProjectReferenceFilter do
include FilterSpecHelper
def invalidate_reference(reference)
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::ReferenceFilter do
RSpec.describe Banzai::Filter::References::ReferenceFilter do
let(:project) { build_stubbed(:project) }
describe '#each_node' do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::SnippetReferenceFilter do
RSpec.describe Banzai::Filter::References::SnippetReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Banzai::Filter::UserReferenceFilter do
RSpec.describe Banzai::Filter::References::UserReferenceFilter do
include FilterSpecHelper
def get_reference(user)
......
......@@ -25,7 +25,7 @@ RSpec.describe Banzai::Pipeline::GfmPipeline do
issue = create(:issue, project: project)
markdown = "text #{issue.to_reference(project, full: true)}"
expect_any_instance_of(Banzai::Filter::ReferenceFilter).to receive(:each_node).once
expect_any_instance_of(Banzai::Filter::References::ReferenceFilter).to receive(:each_node).once
described_class.call(markdown, project: project)
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