feat(support): add realtime case conversations
This commit is contained in:
parent
a9984738cc
commit
39b7d0c6a3
|
|
@ -13,6 +13,13 @@ defmodule WhoNeedHelp.Support do
|
||||||
alias WhoNeedHelp.Trust.RateLimiter
|
alias WhoNeedHelp.Trust.RateLimiter
|
||||||
|
|
||||||
@access_salt "support-request-access"
|
@access_salt "support-request-access"
|
||||||
|
@staff_topic "support:staff"
|
||||||
|
|
||||||
|
def subscribe_request(id),
|
||||||
|
do: Phoenix.PubSub.subscribe(WhoNeedHelp.PubSub, request_topic(id))
|
||||||
|
|
||||||
|
def subscribe_staff,
|
||||||
|
do: Phoenix.PubSub.subscribe(WhoNeedHelp.PubSub, @staff_topic)
|
||||||
|
|
||||||
def change_request(%SupportRequest{} = request, attrs \\ %{}) do
|
def change_request(%SupportRequest{} = request, attrs \\ %{}) do
|
||||||
SupportRequest.submission_changeset(request, attrs)
|
SupportRequest.submission_changeset(request, attrs)
|
||||||
|
|
@ -56,6 +63,7 @@ defmodule WhoNeedHelp.Support do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> notify_received()
|
|> notify_received()
|
||||||
|
|> broadcast_request_update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -201,6 +209,7 @@ defmodule WhoNeedHelp.Support do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> notify_decision()
|
|> notify_decision()
|
||||||
|
|> broadcast_request_update()
|
||||||
else
|
else
|
||||||
false -> {:error, :forbidden}
|
false -> {:error, :forbidden}
|
||||||
_ -> {:error, :not_found}
|
_ -> {:error, :not_found}
|
||||||
|
|
@ -269,6 +278,7 @@ defmodule WhoNeedHelp.Support do
|
||||||
{:ok, {updated, _message}} -> {:ok, preload_conversation(updated)}
|
{:ok, {updated, _message}} -> {:ok, preload_conversation(updated)}
|
||||||
error -> error
|
error -> error
|
||||||
end)
|
end)
|
||||||
|
|> broadcast_request_update()
|
||||||
else
|
else
|
||||||
nil -> {:error, :invalid_message}
|
nil -> {:error, :invalid_message}
|
||||||
{:error, _reason} = error -> error
|
{:error, _reason} = error -> error
|
||||||
|
|
@ -313,6 +323,7 @@ defmodule WhoNeedHelp.Support do
|
||||||
{:ok, updated} -> {:ok, preload_conversation(updated)}
|
{:ok, updated} -> {:ok, preload_conversation(updated)}
|
||||||
error -> error
|
error -> error
|
||||||
end)
|
end)
|
||||||
|
|> broadcast_request_update()
|
||||||
else
|
else
|
||||||
false -> {:error, :invalid_status}
|
false -> {:error, :invalid_status}
|
||||||
{:error, _reason} = error -> error
|
{:error, _reason} = error -> error
|
||||||
|
|
@ -371,6 +382,19 @@ defmodule WhoNeedHelp.Support do
|
||||||
|
|
||||||
defp notify_operator_update(result), do: result
|
defp notify_operator_update(result), do: result
|
||||||
|
|
||||||
|
defp broadcast_request_update({:ok, %SupportRequest{} = request} = result) do
|
||||||
|
event = {:support_request_updated, request.id}
|
||||||
|
|
||||||
|
Phoenix.PubSub.broadcast(WhoNeedHelp.PubSub, request_topic(request.id), event)
|
||||||
|
Phoenix.PubSub.broadcast(WhoNeedHelp.PubSub, @staff_topic, event)
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
defp broadcast_request_update(result), do: result
|
||||||
|
|
||||||
|
defp request_topic(id), do: "support:request:#{id}"
|
||||||
|
|
||||||
defp verify_contact(%SupportRequest{contact_verified_at: nil} = request) do
|
defp verify_contact(%SupportRequest{contact_verified_at: nil} = request) do
|
||||||
request
|
request
|
||||||
|> Ecto.Changeset.change(contact_verified_at: DateTime.utc_now(:second))
|
|> Ecto.Changeset.change(contact_verified_at: DateTime.utc_now(:second))
|
||||||
|
|
|
||||||
|
|
@ -101,15 +101,13 @@ defmodule WhoNeedHelpWeb.SupportController do
|
||||||
|> put_flash(:info, gettext("Your message was sent to support."))
|
|> put_flash(:info, gettext("Your message was sent to support."))
|
||||||
|> redirect(to: case_path(request.id, token))
|
|> redirect(to: case_path(request.id, token))
|
||||||
|
|
||||||
{:error, %Ecto.Changeset{} = changeset} ->
|
{:error, %Ecto.Changeset{}} ->
|
||||||
with {:ok, request} <-
|
conn
|
||||||
Support.get_for_viewer(conn.assigns.current_scope, id, token) do
|
|> put_flash(
|
||||||
conn
|
:error,
|
||||||
|> put_status(:unprocessable_entity)
|
gettext("Could not send your message. Please check it and try again.")
|
||||||
|> render_case(request, token, changeset)
|
)
|
||||||
else
|
|> redirect(to: case_path(id, token))
|
||||||
_ -> send_resp(conn, :not_found, "Not found")
|
|
||||||
end
|
|
||||||
|
|
||||||
{:error, :rate_limited} ->
|
{:error, :rate_limited} ->
|
||||||
conn
|
conn
|
||||||
|
|
@ -156,18 +154,12 @@ defmodule WhoNeedHelpWeb.SupportController do
|
||||||
|
|
||||||
def received(conn, _params), do: redirect(conn, to: ~p"/support")
|
def received(conn, _params), do: redirect(conn, to: ~p"/support")
|
||||||
|
|
||||||
defp render_case(conn, request, token, changeset \\ nil) do
|
defp render_case(conn, request, token) do
|
||||||
message_form =
|
Phoenix.LiveView.Controller.live_render(conn, WhoNeedHelpWeb.SupportCaseLive,
|
||||||
case changeset do
|
session: %{
|
||||||
%Ecto.Changeset{} -> Phoenix.Component.to_form(changeset, as: :message)
|
"support_request_id" => request.id,
|
||||||
nil -> Phoenix.Component.to_form(%{"body" => ""}, as: :message)
|
"support_access_token" => token
|
||||||
end
|
}
|
||||||
|
|
||||||
render(conn, :show,
|
|
||||||
request: request,
|
|
||||||
access_token: token,
|
|
||||||
message_form: message_form,
|
|
||||||
reopen_form: Phoenix.Component.to_form(%{}, as: :reopen)
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,18 @@
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-sm font-semibold text-success">{gettext("REQUEST RECEIVED")}</div>
|
<div class="text-sm font-semibold text-success">{gettext("REQUEST RECEIVED")}</div>
|
||||||
<h1 class="mt-2 text-4xl font-black">{gettext("Check your email")}</h1>
|
<h1 class="mt-2 text-4xl font-black">
|
||||||
<p class="mt-4 text-base-content/65">
|
{if @current_scope,
|
||||||
|
do: gettext("Support request created"),
|
||||||
|
else: gettext("Check your email")}
|
||||||
|
</h1>
|
||||||
|
<p :if={@current_scope} class="mt-4 text-base-content/65">
|
||||||
|
{gettext(
|
||||||
|
"We created support request %{reference} and added it to your account. You can open it now and return to it from My support requests.",
|
||||||
|
reference: @reference
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p :if={is_nil(@current_scope)} class="mt-4 text-base-content/65">
|
||||||
{gettext(
|
{gettext(
|
||||||
"We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests.",
|
"We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests.",
|
||||||
reference: @reference
|
reference: @reference
|
||||||
|
|
|
||||||
|
|
@ -83,13 +83,16 @@
|
||||||
|
|
||||||
<.form
|
<.form
|
||||||
for={@message_form}
|
for={@message_form}
|
||||||
|
id="support-message-form"
|
||||||
action={~p"/support/cases/#{@request.id}/messages"}
|
action={~p"/support/cases/#{@request.id}/messages"}
|
||||||
method="post"
|
method="post"
|
||||||
|
phx-submit="send-message"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
>
|
>
|
||||||
<input :if={@access_token} type="hidden" name="token" value={@access_token} />
|
<input :if={@access_token} type="hidden" name="token" value={@access_token} />
|
||||||
<.input
|
<.input
|
||||||
field={@message_form[:body]}
|
field={@message_form[:body]}
|
||||||
|
id={"support-message-body-#{@message_form_version}"}
|
||||||
type="textarea"
|
type="textarea"
|
||||||
label={gettext("Message to support")}
|
label={gettext("Message to support")}
|
||||||
placeholder={gettext("Add details or answer the support team")}
|
placeholder={gettext("Add details or answer the support team")}
|
||||||
|
|
@ -100,8 +103,10 @@
|
||||||
<.form
|
<.form
|
||||||
:if={@request.status in [:resolved, :closed]}
|
:if={@request.status in [:resolved, :closed]}
|
||||||
for={@reopen_form}
|
for={@reopen_form}
|
||||||
|
id="support-reopen-form"
|
||||||
action={~p"/support/cases/#{@request.id}/reopen"}
|
action={~p"/support/cases/#{@request.id}/reopen"}
|
||||||
method="post"
|
method="post"
|
||||||
|
phx-submit="reopen"
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
>
|
>
|
||||||
<input :if={@access_token} type="hidden" name="token" value={@access_token} />
|
<input :if={@access_token} type="hidden" name="token" value={@access_token} />
|
||||||
|
|
|
||||||
127
lib/who_need_help_web/live/support_case_live.ex
Normal file
127
lib/who_need_help_web/live/support_case_live.ex
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
defmodule WhoNeedHelpWeb.SupportCaseLive do
|
||||||
|
use WhoNeedHelpWeb, :live_view
|
||||||
|
|
||||||
|
alias WhoNeedHelp.Support
|
||||||
|
alias WhoNeedHelpWeb.SupportHTML
|
||||||
|
|
||||||
|
on_mount {WhoNeedHelpWeb.UserAuth, :mount_current_scope}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(
|
||||||
|
_params,
|
||||||
|
%{
|
||||||
|
"support_request_id" => request_id,
|
||||||
|
"support_access_token" => access_token
|
||||||
|
},
|
||||||
|
socket
|
||||||
|
) do
|
||||||
|
case Support.get_for_viewer(socket.assigns.current_scope, request_id, access_token) do
|
||||||
|
{:ok, request} ->
|
||||||
|
if connected?(socket), do: Support.subscribe_request(request.id)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, request.subject)
|
||||||
|
|> assign(:request, request)
|
||||||
|
|> assign(:access_token, access_token)
|
||||||
|
|> assign(:message_form_version, 0)
|
||||||
|
|> assign_message_form()
|
||||||
|
|> assign(:reopen_form, to_form(%{}, as: :reopen))}
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("This support request is no longer available."))
|
||||||
|
|> redirect(to: ~p"/support")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("send-message", %{"message" => params}, socket) do
|
||||||
|
case Support.add_requester_message(
|
||||||
|
socket.assigns.current_scope,
|
||||||
|
socket.assigns.request.id,
|
||||||
|
socket.assigns.access_token,
|
||||||
|
params
|
||||||
|
) do
|
||||||
|
{:ok, request} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:request, request)
|
||||||
|
|> update(:message_form_version, &(&1 + 1))
|
||||||
|
|> assign_message_form()
|
||||||
|
|> put_flash(:info, gettext("Your message was sent to support."))}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, :message_form, to_form(changeset, as: :message))}
|
||||||
|
|
||||||
|
{:error, :rate_limited} ->
|
||||||
|
{:noreply,
|
||||||
|
put_flash(socket, :error, gettext("Too many messages. Please try again later."))}
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("This support request is no longer available."))
|
||||||
|
|> redirect(to: ~p"/support")}
|
||||||
|
|
||||||
|
{:error, _reason} ->
|
||||||
|
{:noreply,
|
||||||
|
put_flash(
|
||||||
|
socket,
|
||||||
|
:error,
|
||||||
|
gettext("Could not send your message. Please check it and try again.")
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("reopen", _params, socket) do
|
||||||
|
case Support.reopen_request(
|
||||||
|
socket.assigns.current_scope,
|
||||||
|
socket.assigns.request.id,
|
||||||
|
socket.assigns.access_token
|
||||||
|
) do
|
||||||
|
{:ok, request} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:request, request)
|
||||||
|
|> put_flash(:info, gettext("The support request is open again."))}
|
||||||
|
|
||||||
|
{:error, :invalid_status} ->
|
||||||
|
{:noreply,
|
||||||
|
put_flash(
|
||||||
|
socket,
|
||||||
|
:error,
|
||||||
|
gettext("Only a resolved or closed request can be reopened.")
|
||||||
|
)}
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:error, gettext("This support request is no longer available."))
|
||||||
|
|> redirect(to: ~p"/support")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(
|
||||||
|
{:support_request_updated, request_id},
|
||||||
|
%{assigns: %{request: %{id: request_id}}} = socket
|
||||||
|
) do
|
||||||
|
case Support.get_for_viewer(
|
||||||
|
socket.assigns.current_scope,
|
||||||
|
request_id,
|
||||||
|
socket.assigns.access_token
|
||||||
|
) do
|
||||||
|
{:ok, request} -> {:noreply, assign(socket, :request, request)}
|
||||||
|
{:error, :not_found} -> {:noreply, redirect(socket, to: ~p"/support")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_message_form(socket) do
|
||||||
|
assign(socket, :message_form, to_form(%{"body" => ""}, as: :message))
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns), do: SupportHTML.show(assigns)
|
||||||
|
end
|
||||||
|
|
@ -5,6 +5,8 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
|
if connected?(socket), do: Support.subscribe_staff()
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, gettext("Support operations"))
|
|> assign(:page_title, gettext("Support operations"))
|
||||||
|
|
@ -12,6 +14,11 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
|
||||||
|> load()}
|
|> load()}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:support_request_updated, _request_id}, socket) do
|
||||||
|
{:noreply, load(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("moderate-support", %{"id" => id, "moderation" => params}, socket) do
|
def handle_event("moderate-support", %{"id" => id, "moderation" => params}, socket) do
|
||||||
respond(
|
respond(
|
||||||
|
|
@ -196,29 +203,44 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
|
||||||
</p>
|
</p>
|
||||||
<.form
|
<.form
|
||||||
for={form}
|
for={form}
|
||||||
|
id={"removal-moderation-#{notice.id}"}
|
||||||
phx-submit="moderate-removal"
|
phx-submit="moderate-removal"
|
||||||
phx-value-id={notice.id}
|
phx-value-id={notice.id}
|
||||||
class="mt-4 grid gap-2 md:grid-cols-[13rem_1fr_auto]"
|
class="mt-4 rounded-2xl border border-error/20 bg-error/5 p-4"
|
||||||
>
|
>
|
||||||
<.input
|
<div>
|
||||||
field={form[:status]}
|
<h4 class="font-bold">{gettext("Operator action")}</h4>
|
||||||
type="select"
|
<p class="mt-1 text-sm text-base-content/60">
|
||||||
options={[
|
{gettext("Record the decision and notify the submitter when contact is available.")}
|
||||||
{gettext("Open"), "open"},
|
</p>
|
||||||
{gettext("Urgent review"), "urgent_review"},
|
</div>
|
||||||
{gettext("Reviewing"), "reviewing"},
|
<div class="mt-3 grid gap-3 md:grid-cols-[13rem_minmax(0,1fr)_auto] md:items-end">
|
||||||
{gettext("More information needed"), "needs_information"},
|
<.input
|
||||||
{gettext("Action taken"), "actioned"},
|
field={form[:status]}
|
||||||
{gettext("Rejected"), "rejected"},
|
type="select"
|
||||||
{gettext("Closed"), "closed"}
|
label={gettext("Status")}
|
||||||
]}
|
options={[
|
||||||
/>
|
{gettext("Open"), "open"},
|
||||||
<.input
|
{gettext("Urgent review"), "urgent_review"},
|
||||||
field={form[:resolution_note]}
|
{gettext("Reviewing"), "reviewing"},
|
||||||
type="textarea"
|
{gettext("More information needed"), "needs_information"},
|
||||||
placeholder={gettext("Decision or information request")}
|
{gettext("Action taken"), "actioned"},
|
||||||
/>
|
{gettext("Rejected"), "rejected"},
|
||||||
<.button class="btn btn-error self-end">{gettext("Save and notify")}</.button>
|
{gettext("Closed"), "closed"}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<.input
|
||||||
|
field={form[:resolution_note]}
|
||||||
|
type="textarea"
|
||||||
|
rows="2"
|
||||||
|
label={gettext("Decision or information request")}
|
||||||
|
placeholder={gettext("Explain the decision or request the missing information")}
|
||||||
|
class="textarea min-h-20 w-full resize-y"
|
||||||
|
/>
|
||||||
|
<.button class="btn btn-error mb-2 min-w-40">
|
||||||
|
{gettext("Save and notify")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
</article>
|
</article>
|
||||||
<p :if={@removal_notices == []} class="text-base-content/50">
|
<p :if={@removal_notices == []} class="text-base-content/50">
|
||||||
|
|
@ -333,27 +355,42 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
|
||||||
</div>
|
</div>
|
||||||
<.form
|
<.form
|
||||||
for={form}
|
for={form}
|
||||||
|
id={"support-moderation-#{request.id}"}
|
||||||
phx-submit="moderate-support"
|
phx-submit="moderate-support"
|
||||||
phx-value-id={request.id}
|
phx-value-id={request.id}
|
||||||
class="mt-4 grid gap-2 md:grid-cols-[13rem_1fr_auto]"
|
class="mt-4 rounded-2xl border border-primary/20 bg-primary/5 p-4"
|
||||||
>
|
>
|
||||||
<.input
|
<div>
|
||||||
field={form[:status]}
|
<h4 class="font-bold">{gettext("Operator action")}</h4>
|
||||||
type="select"
|
<p class="mt-1 text-sm text-base-content/60">
|
||||||
options={[
|
{gettext("Update the case status and add a private reply when needed.")}
|
||||||
{gettext("Open"), "open"},
|
</p>
|
||||||
{gettext("Reviewing"), "reviewing"},
|
</div>
|
||||||
{gettext("Waiting for requester"), "waiting_for_requester"},
|
<div class="mt-3 grid gap-3 md:grid-cols-[13rem_minmax(0,1fr)_auto] md:items-end">
|
||||||
{gettext("Resolved"), "resolved"},
|
<.input
|
||||||
{gettext("Closed"), "closed"}
|
field={form[:status]}
|
||||||
]}
|
type="select"
|
||||||
/>
|
label={gettext("Status")}
|
||||||
<.input
|
options={[
|
||||||
field={form[:response]}
|
{gettext("Open"), "open"},
|
||||||
type="textarea"
|
{gettext("Reviewing"), "reviewing"},
|
||||||
placeholder={gettext("Reply to the requester (optional)")}
|
{gettext("Waiting for requester"), "waiting_for_requester"},
|
||||||
/>
|
{gettext("Resolved"), "resolved"},
|
||||||
<.button class="btn btn-primary self-end">{gettext("Save and notify")}</.button>
|
{gettext("Closed"), "closed"}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<.input
|
||||||
|
field={form[:response]}
|
||||||
|
type="textarea"
|
||||||
|
rows="2"
|
||||||
|
label={gettext("Private reply (optional)")}
|
||||||
|
placeholder={gettext("Write a clear reply for the requester")}
|
||||||
|
class="textarea min-h-20 w-full resize-y"
|
||||||
|
/>
|
||||||
|
<.button class="btn btn-primary mb-2 min-w-40">
|
||||||
|
{gettext("Save and notify")}
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
</article>
|
</article>
|
||||||
<p :if={@support_requests == []} class="text-base-content/50">
|
<p :if={@support_requests == []} class="text-base-content/50">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule WhoNeedHelpWeb.SupportControllerTest do
|
defmodule WhoNeedHelpWeb.SupportControllerTest do
|
||||||
use WhoNeedHelpWeb.ConnCase, async: false
|
use WhoNeedHelpWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
import WhoNeedHelp.AccountsFixtures
|
import WhoNeedHelp.AccountsFixtures
|
||||||
import Swoosh.TestAssertions
|
import Swoosh.TestAssertions
|
||||||
|
|
||||||
|
|
@ -80,6 +81,18 @@ defmodule WhoNeedHelpWeb.SupportControllerTest do
|
||||||
describe "authenticated support" do
|
describe "authenticated support" do
|
||||||
setup :register_and_log_in_user
|
setup :register_and_log_in_user
|
||||||
|
|
||||||
|
test "receipt gives an authenticated requester immediate account access", %{conn: conn} do
|
||||||
|
page =
|
||||||
|
conn
|
||||||
|
|> get(~p"/support/received?reference=SUP-ACCOUNT")
|
||||||
|
|> html_response(200)
|
||||||
|
|
||||||
|
assert page =~ "Support request created"
|
||||||
|
assert page =~ "added it to your account"
|
||||||
|
assert page =~ "View my requests"
|
||||||
|
refute page =~ "<h1 class=\"mt-2 text-4xl font-black\">Check your email</h1>"
|
||||||
|
end
|
||||||
|
|
||||||
test "lists the current user's support cases and removal notices", %{
|
test "lists the current user's support cases and removal notices", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
user: user
|
user: user
|
||||||
|
|
@ -181,6 +194,62 @@ defmodule WhoNeedHelpWeb.SupportControllerTest do
|
||||||
assert page =~ "Open"
|
assert page =~ "Open"
|
||||||
refute page =~ "Reopen without a message"
|
refute page =~ "Reopen without a message"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "requester and operator receive support conversation updates in real time", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
previous_test_process = Application.get_env(:swoosh, :shared_test_process)
|
||||||
|
Application.put_env(:swoosh, :shared_test_process, self())
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Application.put_env(:swoosh, :shared_test_process, previous_test_process)
|
||||||
|
end)
|
||||||
|
|
||||||
|
scope = user_scope_fixture(user)
|
||||||
|
|
||||||
|
{:ok, request} =
|
||||||
|
WhoNeedHelp.Support.create_request(scope, %{
|
||||||
|
"kind" => "technical_issue",
|
||||||
|
"subject" => "Realtime support conversation",
|
||||||
|
"details" => "Both connected support views must refresh without a browser reload."
|
||||||
|
})
|
||||||
|
|
||||||
|
moderator =
|
||||||
|
user_fixture()
|
||||||
|
|> Ecto.Changeset.change(role: :moderator)
|
||||||
|
|> WhoNeedHelp.Repo.update!()
|
||||||
|
|
||||||
|
{:ok, requester_view, _html} = live(conn, ~p"/support/cases/#{request.id}")
|
||||||
|
|
||||||
|
{:ok, operator_view, _html} =
|
||||||
|
build_conn()
|
||||||
|
|> log_in_user(moderator)
|
||||||
|
|> live(~p"/support/operations")
|
||||||
|
|
||||||
|
operator_view
|
||||||
|
|> form("#support-moderation-#{request.id}", %{
|
||||||
|
"moderation" => %{
|
||||||
|
"status" => "reviewing",
|
||||||
|
"response" => "We are reviewing this case now."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
assert render(requester_view) =~ "We are reviewing this case now."
|
||||||
|
assert render(requester_view) =~ "reviewing"
|
||||||
|
|
||||||
|
requester_view
|
||||||
|
|> form("#support-message-form", %{
|
||||||
|
"message" => %{"body" => "Here is the requested follow-up."}
|
||||||
|
})
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
assert render(operator_view) =~ "Here is the requested follow-up."
|
||||||
|
|
||||||
|
refute render(element(requester_view, "#support-message-form")) =~
|
||||||
|
"Here is the requested follow-up."
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "operator queue authorization" do
|
describe "operator queue authorization" do
|
||||||
|
|
@ -240,6 +309,17 @@ defmodule WhoNeedHelpWeb.SupportControllerTest do
|
||||||
"select[id^='support-'] option[value='open'][selected]"
|
"select[id^='support-'] option[value='open'][selected]"
|
||||||
)
|
)
|
||||||
) == 1
|
) == 1
|
||||||
|
|
||||||
|
assert Enum.count(
|
||||||
|
LazyHTML.query(document, "form[id^='removal-moderation-'] textarea[rows='2']")
|
||||||
|
) == 1
|
||||||
|
|
||||||
|
assert Enum.count(
|
||||||
|
LazyHTML.query(document, "form[id^='support-moderation-'] textarea[rows='2']")
|
||||||
|
) == 1
|
||||||
|
|
||||||
|
assert response =~ "Operator action"
|
||||||
|
assert response =~ "Private reply (optional)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user