Commit 2f02cf4e authored by Vitali Tatarintev's avatar Vitali Tatarintev

Move TimelineEventPipeline into IncidentManagement namespace

* Move TimelineEventPipeline into IncidentManagement namespace
* Cover additional scenarios in tests
parent 7982f091
...@@ -6,7 +6,7 @@ module IncidentManagement ...@@ -6,7 +6,7 @@ module IncidentManagement
self.table_name = 'incident_management_timeline_events' self.table_name = 'incident_management_timeline_events'
cache_markdown_field :note, pipeline: :timeline_event, issuable_reference_expansion_enabled: true cache_markdown_field :note, pipeline: :'incident_management/timeline_event', issuable_reference_expansion_enabled: true
belongs_to :project belongs_to :project
belongs_to :author, class_name: 'User', foreign_key: :author_id belongs_to :author, class_name: 'User', foreign_key: :author_id
......
# frozen_string_literal: true
module Banzai
module Pipeline
module IncidentManagement
class TimelineEventPipeline < PlainMarkdownPipeline
ALLOWLIST = Banzai::Filter::SanitizationFilter::LIMITED.deep_dup.merge(
elements: %w(p b i strong em pre code a img)
).freeze
def self.filters
@filters ||= FilterArray[
*super,
*Banzai::Pipeline::GfmPipeline.reference_filters,
Filter::EmojiFilter,
Filter::ExternalLinkFilter,
Filter::ImageLinkFilter,
Filter::SanitizationFilter
]
end
def self.transform_context(context)
Filter::AssetProxyFilter.transform_context(context).merge(
only_path: true,
no_sourcepos: true,
allowlist: ALLOWLIST,
link_replaces_image: true
)
end
end
end
end
end
# frozen_string_literal: true
module Banzai
module Pipeline
class TimelineEventPipeline < PlainMarkdownPipeline
ALLOWLIST = Banzai::Filter::SanitizationFilter::LIMITED.deep_dup.merge(
elements: %w(p b i strong em pre code a img)
)
def self.filters
@filters ||= FilterArray[
*super,
*reference_filters,
Filter::EmojiFilter,
Filter::ExternalLinkFilter,
Filter::ImageLinkFilter,
Filter::SanitizationFilter
]
end
def self.reference_filters
Banzai::Pipeline::GfmPipeline.reference_filters
end
def self.transform_context(context)
Filter::AssetProxyFilter.transform_context(context).merge(
only_path: true,
no_sourcepos: true,
allowlist: ALLOWLIST,
link_replaces_image: true
)
end
end
end
end
...@@ -2,13 +2,20 @@ ...@@ -2,13 +2,20 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Banzai::Pipeline::TimelineEventPipeline do RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
describe '.reference_filters' do describe '.filters' do
it 'contains required reference filters' do it 'contains required filters' do
expect(described_class.reference_filters).to contain_exactly( expect(described_class.filters).to eq(
*Banzai::Pipeline::GfmPipeline.reference_filters [
*Banzai::Pipeline::PlainMarkdownPipeline.filters,
*Banzai::Pipeline::GfmPipeline.reference_filters,
Banzai::Filter::EmojiFilter,
Banzai::Filter::ExternalLinkFilter,
Banzai::Filter::ImageLinkFilter,
Banzai::Filter::SanitizationFilter
]
) )
end end
end end
...@@ -17,13 +24,13 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do ...@@ -17,13 +24,13 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do
subject(:output) { described_class.to_html(markdown, project: project) } subject(:output) { described_class.to_html(markdown, project: project) }
context 'when markdown contains font style transformations' do context 'when markdown contains font style transformations' do
let(:markdown) { '**bold** _italic_ `code`'} let(:markdown) { '**bold** _italic_ `code`' }
it { is_expected.to eq('<p><strong>bold</strong> <em>italic</em> <code>code</code></p>') } it { is_expected.to eq('<p><strong>bold</strong> <em>italic</em> <code>code</code></p>') }
end end
context 'when markdown contains banned HTML tags' do context 'when markdown contains banned HTML tags' do
let(:markdown) { '<div>div</div><h1>h1</h1>'} let(:markdown) { '<div>div</div><h1>h1</h1>' }
it 'filters out banned tags' do it 'filters out banned tags' do
is_expected.to eq(' div h1 ') is_expected.to eq(' div h1 ')
...@@ -40,12 +47,12 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do ...@@ -40,12 +47,12 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do
let(:markdown) { '![Name](/path/to/image.png)' } let(:markdown) { '![Name](/path/to/image.png)' }
it 'replaces image with a link to the image' do it 'replaces image with a link to the image' do
is_expected.to eq(%q{<p><a class="with-attachment-icon" href="/path/to/image.png" target="_blank">Name</a></p>}) is_expected.to eq(%q(<p><a class="with-attachment-icon" href="/path/to/image.png" target="_blank">Name</a></p>))
end end
end end
context 'when markdown contains emojis' do context 'when markdown contains emojis' do
let(:markdown) { ':+1:👍'} let(:markdown) { ':+1:👍' }
it { is_expected.to eq('<p>👍👍</p>') } it { is_expected.to eq('<p>👍👍</p>') }
end end
...@@ -55,7 +62,7 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do ...@@ -55,7 +62,7 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do
let(:markdown) { "issue ##{issue.iid}" } let(:markdown) { "issue ##{issue.iid}" }
it 'contains a link to the issue' do it 'contains a link to the issue' do
is_expected.to match(%r(<p>issue <a\shref="[\w\/]+-\/issues\/#{issue.iid}".*>##{issue.iid}<\/a><\/p>)) is_expected.to match(%r(<p>issue <a href="[\w/]+-/issues/#{issue.iid}".*>##{issue.iid}</a></p>))
end end
end end
...@@ -64,7 +71,7 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do ...@@ -64,7 +71,7 @@ RSpec.describe Banzai::Pipeline::TimelineEventPipeline do
let(:markdown) { "MR !#{mr.iid}" } let(:markdown) { "MR !#{mr.iid}" }
it 'contains a link to the merge request' do it 'contains a link to the merge request' do
is_expected.to match(%r(<p>MR <a\shref="[\w\/]+-\/merge_requests\/#{mr.iid}".*>!#{mr.iid}<\/a><\/p>)) is_expected.to match(%r(<p>MR <a href="[\w/]+-/merge_requests/#{mr.iid}".*>!#{mr.iid}</a></p>))
end end
end end
end end
......
...@@ -9,7 +9,7 @@ module Banzai ...@@ -9,7 +9,7 @@ module Banzai
# a new node (a link to the image source), copy the image as a child # a new node (a link to the image source), copy the image as a child
# of the anchor, and then replace the img with the link-wrapped version. # of the anchor, and then replace the img with the link-wrapped version.
# #
# If `link_replaces_image` context parameter provided, the image is going # If `link_replaces_image` context parameter is provided, the image is going
# to be replaced with a link to an image. # to be replaced with a link to an image.
def call def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img| doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
......
...@@ -8,29 +8,34 @@ RSpec.describe Banzai::Filter::ImageLinkFilter do ...@@ -8,29 +8,34 @@ RSpec.describe Banzai::Filter::ImageLinkFilter do
let(:path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' } let(:path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
let(:context) { {} } let(:context) { {} }
def image(path, alt: nil) def image(path, alt: nil, data_src: nil)
alt_tag = alt ? %Q{alt="#{alt}"} : "" alt_tag = alt ? %Q{alt="#{alt}"} : ""
data_src_tag = data_src ? %Q{data-src="#{data_src}"} : ""
%(<img src="#{path}" #{alt_tag} />) %(<img src="#{path}" #{alt_tag} #{data_src_tag} />)
end end
it 'wraps the image with a link to the image src' do it 'wraps the image with a link to the image src' do
doc = filter(image(path), context) doc = filter(image(path), context)
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end end
it 'does not wrap a duplicate link' do it 'does not wrap a duplicate link' do
doc = filter(%Q(<a href="/whatever">#{image(path)}</a>), context) doc = filter(%Q(<a href="/whatever">#{image(path)}</a>), context)
expect(doc.to_html).to match %r{^<a href="/whatever"><img[^>]*></a>$} expect(doc.to_html).to match %r{^<a href="/whatever"><img[^>]*></a>$}
end end
it 'works with external images' do it 'works with external images' do
doc = filter(image('https://i.imgur.com/DfssX9C.jpg'), context) doc = filter(image('https://i.imgur.com/DfssX9C.jpg'), context)
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end end
it 'works with inline images' do it 'works with inline images' do
doc = filter(%Q(<p>test #{image(path)} inline</p>), context) doc = filter(%Q(<p>test #{image(path)} inline</p>), context)
expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$} expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$}
end end
...@@ -41,11 +46,18 @@ RSpec.describe Banzai::Filter::ImageLinkFilter do ...@@ -41,11 +46,18 @@ RSpec.describe Banzai::Filter::ImageLinkFilter do
expect(doc.at_css('img')['data-canonical-src']).to eq doc.at_css('a')['data-canonical-src'] expect(doc.at_css('img')['data-canonical-src']).to eq doc.at_css('a')['data-canonical-src']
end end
it 'adds no-attachment icon class to the link' do
doc = filter(image(path), context)
expect(doc.at_css('a')['class']).to match(%r{no-attachment-icon})
end
context 'when :link_replaces_image is true' do context 'when :link_replaces_image is true' do
let(:context) { { link_replaces_image: true } } let(:context) { { link_replaces_image: true } }
it 'replaces the image with link to image src', :aggregate_failures do it 'replaces the image with link to image src', :aggregate_failures do
doc = filter(image(path), context) doc = filter(image(path), context)
expect(doc.to_html).to match(%r{^<a[^>]*>#{path}</a>$}) expect(doc.to_html).to match(%r{^<a[^>]*>#{path}</a>$})
expect(doc.at_css('a')['href']).to eq(path) expect(doc.at_css('a')['href']).to eq(path)
end end
...@@ -57,6 +69,14 @@ RSpec.describe Banzai::Filter::ImageLinkFilter do ...@@ -57,6 +69,14 @@ RSpec.describe Banzai::Filter::ImageLinkFilter do
expect(doc.at_css('a')['href']).to eq(path) expect(doc.at_css('a')['href']).to eq(path)
end end
it 'uses image data-src as a link text', :aggregate_failures do
data_src = '/uploads/data-src.png'
doc = filter(image(path, data_src: data_src), context)
expect(doc.to_html).to match(%r{^<a[^>]*>#{data_src}</a>$})
expect(doc.at_css('a')['href']).to eq(data_src)
end
it 'adds attachment icon class to the link' do it 'adds attachment icon class to the link' do
doc = filter(image(path), context) doc = filter(image(path), context)
......
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