Skip to content

Commit

Permalink
Show attachments in viewer (#553)
Browse files Browse the repository at this point in the history
Fixes #520

What changed?
============

Adds support for viewing attachments in SentEmailViewerPlug.

It be tested manually by running `mix bamboo.start_sent_email_viewer`. 

Co-authored-by: Nathan Hadfield <nathan.hadfield.adm@divvypay.com>
  • Loading branch information
hadfieldn and Nathan Hadfield authored Oct 13, 2020
1 parent 6f2662e commit 198b504
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 2 deletions.
16 changes: 16 additions & 0 deletions lib/bamboo/plug/sent_email_viewer/email_preview_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ defmodule Bamboo.SentEmailViewerPlug do
end
end

get "/:id/attachments/:index" do
with %Bamboo.Email{} = email <- SentEmail.get(id),
%Bamboo.Attachment{} = attachment <- Enum.at(email.attachments, String.to_integer(index)) do
conn
|> Plug.Conn.put_resp_header(
"content-disposition",
"inline; filename=\"#{attachment.filename}\""
)
|> Plug.Conn.put_resp_content_type(attachment.content_type)
|> send_resp(:ok, attachment.data)
else
_ ->
conn |> render_not_found
end
end

defp all_emails do
SentEmail.all()
end
Expand Down
4 changes: 4 additions & 0 deletions lib/bamboo/plug/sent_email_viewer/helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ defmodule Bamboo.SentEmailViewerPlug.Helper do
def format_email_address({name, address}) do
"#{name} &lt;#{address}&gt;"
end

def format_attachment(%{filename: filename}) do
HTML.html_escape(filename)
end
end
57 changes: 55 additions & 2 deletions lib/bamboo/plug/sent_email_viewer/index.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,41 @@
padding: 10px 0;
}

.email-attachment-icon::after {
content: '📎';
filter: grayscale(100%);
}

.email-detail-attachments {
display: flex;
align-items: center;
margin: 0.2em 0;
}

.email-detail-attachments-icon-container {
margin: 0 0.25em 0 0;
}

.email-detail-attachments ul {
display: flex;
flex-wrap: wrap;
list-style: none;
padding: 0;
margin: 0;
}

.email-detail-attachments ul li {
padding-right: 0.25em;
}

.email-detail-attachments ul li:not(:last-of-type)::after {
content: ',';
}

.email-detail-attachments ul li a {
font-weight: bold;
}

.email-detail-bodies-container {
border: 1px solid #eee;
border-radius: 5px;
Expand All @@ -123,12 +158,14 @@
}

.email-detail-recipients,
.email-detail-headers {
.email-detail-headers,
.email-detail-attachments {
color: #aaa;
}

.email-detail-recipients strong,
.email-detail-headers strong {
.email-detail-headers strong,
.email-detail-attachments a {
color: #555;
}

Expand All @@ -153,6 +190,7 @@
to <%= Bamboo.SentEmailViewerPlug.Helper.email_addresses(email) %>
</span>
<span class="email-summary-body-excerpt">
<%= if Enum.count(email.attachments) > 0 do %><span class="email-attachment-icon"></span><% end %>
<%= Bamboo.SentEmailViewerPlug.Helper.format_text_body(email.text_body) %>
</span>
</a>
Expand All @@ -172,6 +210,21 @@
</div>
<% end %>
</span>
<% attachment_count = Enum.count(@selected_email.attachments) %>
<%= if attachment_count > 0 do %>
<span class="email-detail-attachments">
<span class="email-detail-attachments-icon-container"><span class="email-attachment-icon"></span></span>
<ul>
<%= for index <- 0..attachment_count - 1 do %>
<%
attachment = Enum.at(@selected_email.attachments, index)
href = "#{@base_path}/#{Bamboo.SentEmail.get_id(@selected_email)}/attachments/#{index}"
%>
<li><a href="<%= href %>"><%= Bamboo.SentEmailViewerPlug.Helper.format_attachment(attachment) %></a></li>
<% end %>
</ul>
</span>
<% end %>
</section>

<section class="email-detail-bodies-container">
Expand Down
19 changes: 19 additions & 0 deletions lib/mix/start_sent_email_viewer_task.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule Mix.Tasks.Bamboo.StartSentEmailViewer do
Me and <em>html tag</em>
"""
)
|> add_attachments(index)
|> Bamboo.Mailer.normalize_addresses()
|> Bamboo.SentEmail.push()
end
Expand All @@ -43,6 +44,24 @@ defmodule Mix.Tasks.Bamboo.StartSentEmailViewer do
no_halt()
end

defp add_attachments(email, count) do
# First attachment will be an image, others will be docx files.
Enum.reduce(count..0, email, fn
0, email ->
email

1, email ->
path = Path.join(__DIR__, "../../logo/logo.png")
label = "bamboo-logo"
Bamboo.Email.put_attachment(email, path, filename: "#{label}.png")

index, email ->
path = Path.join(__DIR__, "../../test/support/attachment.docx")
label = "attachment-#{index}"
Bamboo.Email.put_attachment(email, path, filename: "#{label}.docx")
end)
end

defp no_halt do
unless iex_running?(), do: :timer.sleep(:infinity)
end
Expand Down
79 changes: 79 additions & 0 deletions test/lib/bamboo/plug/sent_email_viewer_plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Bamboo.SentEmailViewerPlugTest do
use Plug.Test
import Bamboo.Factory
alias Bamboo.SentEmail
alias Plug.HTML

defmodule AppRouter do
use Plug.Router
Expand Down Expand Up @@ -120,6 +121,76 @@ defmodule Bamboo.SentEmailViewerPlugTest do
sidebar(conn) |> Floki.find("a.selected-email") |> Floki.text()
end

test "shows attachment icon in sidebar for email with attachments" do
attachment = build(:attachment)
normalize_and_push(:email, attachments: [attachment])
conn = conn(:get, "/sent_emails/foo")

conn = AppRouter.call(conn, nil)

assert conn.status == 200
assert sidebar(conn) |> Floki.find(".selected-email .email-attachment-icon") != []
end

test "does not show attachment icon in sidebar for email without attachments" do
normalize_and_push(:email)
conn = conn(:get, "/sent_emails/foo")

conn = AppRouter.call(conn, nil)

assert conn.status == 200
assert sidebar(conn) |> Floki.find(".selected-email .email-attachment-icon") == []
end

test "does not show attachments if email has none" do
normalize_and_push(:email)
conn = conn(:get, "/sent_emails/foo")

conn = AppRouter.call(conn, nil)

assert conn.status == 200
assert detail_pane_attachments_container(conn) == []
end

test "shows attachments if email has them" do
[attachment1, attachment2] =
attachments = [build(:attachment), build(:attachment, filename: "<b>attach</b>.txt")]

normalize_and_push(:email, attachments: attachments)
conn = conn(:get, "/sent_emails/foo")

conn = AppRouter.call(conn, nil)

assert conn.status == 200
assert showing_in_attachments_container?(conn, attachment1)
assert showing_in_attachments_container?(conn, attachment2)
end

test "handles attachment links" do
attachment = build(:attachment)
normalize_and_push(:email, attachments: [attachment])
selected_email_id = SentEmail.all() |> Enum.at(0) |> SentEmail.get_id()
conn = conn(:get, "/sent_emails/foo/#{selected_email_id}/attachments/0")

conn = AppRouter.call(conn, nil)

assert conn.status == 200

assert {"content-disposition", "inline; filename=\"#{attachment.filename}\""} in conn.resp_headers
end

test "shows error if attachment could not be found" do
normalize_and_push(:email)
selected_email_id = SentEmail.all() |> Enum.at(0) |> SentEmail.get_id()
conn = conn(:get, "/sent_emails/foo/#{selected_email_id}/attachments/0")

conn = AppRouter.call(conn, nil)

assert conn.status == 404
assert {"content-type", "text/html; charset=utf-8"} in conn.resp_headers
assert conn.resp_body =~ "Email not found"
end

test "doesn't have double slash if forwarded at root" do
email = normalize_and_push(:email)
conn = conn(:get, "/")
Expand Down Expand Up @@ -209,4 +280,12 @@ defmodule Bamboo.SentEmailViewerPlugTest do
defp sidebar(conn) do
conn.resp_body |> Floki.find(".email-sidebar")
end

defp detail_pane_attachments_container(conn) do
conn.resp_body |> Floki.find(".email-detail-pane .email-detail-attachments")
end

defp showing_in_attachments_container?(conn, attachment) do
Floki.text(detail_pane_attachments_container(conn)) =~ attachment.filename
end
end
6 changes: 6 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ defmodule Bamboo.Factory do
html_body: "<p>ohai!</p>"
}
end

def attachment_factory do
path = Path.join(__DIR__, "attachment.txt")
filename = sequence(:attachment, &"attachment-#{&1}.txt")
Bamboo.Attachment.new(path, filename: filename)
end
end

0 comments on commit 198b504

Please sign in to comment.