Commit a5d631df authored by nicolasdular's avatar nicolasdular

Add placeholders to broadcast messages

This adds a post processing pipeline which gets triggered for
broadcast messages. The post process pipeline is required to
replace the placeholder with the curren_user's values like name
or email address.
parent 4375dac6
...@@ -47,7 +47,15 @@ module BroadcastMessagesHelper ...@@ -47,7 +47,15 @@ module BroadcastMessagesHelper
end end
def render_broadcast_message(broadcast_message) def render_broadcast_message(broadcast_message)
Banzai.render_field(broadcast_message, :message).html_safe if Feature.enabled?(:broadcast_message_placeholders)
Banzai.render_and_post_process(broadcast_message.message, {
current_user: current_user,
skip_project_check: true,
broadcast_message_placeholders: true
}).html_safe
else
Banzai.render_field(broadcast_message, :message).html_safe
end
end end
def broadcast_type_options def broadcast_type_options
......
# frozen_string_literal: true
module Banzai
module Filter
# Replaces placeholders for broadcast messages with data from the current
# user or the instance.
class BroadcastMessagePlaceholdersFilter < HTML::Pipeline::Filter
def call
return doc unless context[:broadcast_message_placeholders]
doc.traverse { |node| replace_placeholders(node) }
end
private
def replace_placeholders(node)
if node.text? && !node.content.empty?
node.content = replace_content(node.content)
elsif href = link_href(node)
href.value = replace_content(href.value, url_safe_encoding: true)
end
node
end
def link_href(node)
node.element? &&
node.name == 'a' &&
node.attribute_nodes.find { |a| a.name == "href" }
end
def replace_content(content, url_safe_encoding: false)
placeholders.each do |placeholder, method|
regex = Regexp.new("{{#{placeholder}}}|#{CGI.escape("{{#{placeholder}}}")}")
value = url_safe_encoding ? CGI.escape(method.call.to_s) : method.call.to_s
content.gsub!(regex, value)
end
content
end
def placeholders
{
"email" => -> { current_user.try(:email) },
"name" => -> { current_user.try(:name) },
"user_id" => -> { current_user.try(:id) },
"username" => -> { current_user.try(:username) },
"instance_id" => -> { Gitlab::CurrentSettings.try(:uuid) }
}
end
def current_user
context[:current_user]
end
end
end
end
...@@ -8,7 +8,8 @@ module Banzai ...@@ -8,7 +8,8 @@ module Banzai
def self.filters def self.filters
@filters ||= FilterArray[ @filters ||= FilterArray[
*internal_link_filters, *internal_link_filters,
Filter::AbsoluteLinkFilter Filter::AbsoluteLinkFilter,
Filter::BroadcastMessagePlaceholdersFilter
] ]
end end
......
...@@ -57,4 +57,15 @@ describe 'Broadcast Messages' do ...@@ -57,4 +57,15 @@ describe 'Broadcast Messages' do
it_behaves_like 'a dismissable Broadcast Messages' it_behaves_like 'a dismissable Broadcast Messages'
end end
it 'renders broadcast message with placeholders' do
create(:broadcast_message, broadcast_type: 'notification', message: 'Hi {{name}}')
user = create(:user)
sign_in(user)
visit root_path
expect(page).to have_content "Hi #{user.name}"
end
end end
...@@ -27,10 +27,11 @@ describe BroadcastMessagesHelper do ...@@ -27,10 +27,11 @@ describe BroadcastMessagesHelper do
end end
describe 'broadcast_message' do describe 'broadcast_message' do
let_it_be(:user) { create(:user) }
let(:current_broadcast_message) { BroadcastMessage.new(message: 'Current Message') } let(:current_broadcast_message) { BroadcastMessage.new(message: 'Current Message') }
before do before do
allow(helper).to receive(:current_user).and_return(create(:user)) allow(helper).to receive(:current_user).and_return(user)
end end
it 'returns nil when no current message' do it 'returns nil when no current message' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
include FilterSpecHelper
subject { filter(text, current_user: user, broadcast_message_placeholders: true).to_html }
describe 'when current user is set' do
let_it_be(:user) { create(:user, email: "helloworld@example.com", name: "GitLab Tanunki :)") }
context 'replaces placeholder in text' do
let(:text) { 'Email: {{email}}' }
it { expect(subject).to eq("Email: #{user.email}") }
end
context 'replaces placeholder when they are in a link' do
let(:text) { '<a href="http://example.com?email={{email}}"">link</a>' }
it { expect(subject).to eq("<a href=\"http://example.com?email=helloworld%40example.com\">link</a>") }
end
context 'replaces placeholder when they are in an escaped link' do
let(:text) { '<a href="http://example.com?name=%7B%7Bname%7D%7D">link</a>' }
it { expect(subject).to eq("<a href=\"http://example.com?name=GitLab+Tanunki+%3A%29\">link</a>") }
end
context 'works with empty text' do
let(:text) {" "}
it { expect(subject).to eq(" ") }
end
context 'replaces multiple placeholders in a given text' do
let(:text) { "{{email}} {{name}}" }
it { expect(subject).to eq("#{user.email} #{user.name}") }
end
context 'available placeholders' do
context 'replaces the email of the user' do
let(:text) { "{{email}}"}
it { expect(subject).to eq(user.email) }
end
context 'replaces the name of the user' do
let(:text) { "{{name}}"}
it { expect(subject).to eq(user.name) }
end
context 'replaces the ID of the user' do
let(:text) { "{{user_id}}" }
it { expect(subject).to eq(user.id.to_s) }
end
context 'replaces the username of the user' do
let(:text) { "{{username}}" }
it { expect(subject).to eq(user.username) }
end
context 'replaces the instance_id' do
before do
stub_application_setting(uuid: '123')
end
let(:text) { "{{instance_id}}" }
it { expect(subject).to eq(Gitlab::CurrentSettings.uuid) }
end
end
end
describe 'when there is no current user set' do
let(:user) { nil }
context 'replaces placeholder with empty string' do
let(:text) { "Email: {{email}}" }
it { expect(subject).to eq("Email: ") }
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment