fix(moderation): expose reported parent actions
This commit is contained in:
parent
504c0af867
commit
50be881601
|
|
@ -78,6 +78,15 @@ defmodule WhoNeedHelp.Activities.Activity do
|
||||||
activity
|
activity
|
||||||
|> cast(attrs, [:hidden_at, :hidden_reason])
|
|> cast(attrs, [:hidden_at, :hidden_reason])
|
||||||
|> validate_length(:hidden_reason, max: 1_000)
|
|> validate_length(:hidden_reason, max: 1_000)
|
||||||
|
|> require_hidden_reason()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp require_hidden_reason(changeset) do
|
||||||
|
if get_field(changeset, :hidden_at) do
|
||||||
|
validate_required(changeset, :hidden_reason)
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_coordinates(%__MODULE__{
|
def public_coordinates(%__MODULE__{
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,15 @@ defmodule WhoNeedHelp.Help.HelpRequest do
|
||||||
request
|
request
|
||||||
|> cast(attrs, [:hidden_at, :hidden_reason])
|
|> cast(attrs, [:hidden_at, :hidden_reason])
|
||||||
|> validate_length(:hidden_reason, max: 1_000)
|
|> validate_length(:hidden_reason, max: 1_000)
|
||||||
|
|> require_hidden_reason()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp require_hidden_reason(changeset) do
|
||||||
|
if get_field(changeset, :hidden_at) do
|
||||||
|
validate_required(changeset, :hidden_reason)
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cancellation_changeset(request, attrs) do
|
def cancellation_changeset(request, attrs) do
|
||||||
|
|
|
||||||
|
|
@ -345,10 +345,10 @@ defmodule WhoNeedHelp.Trust do
|
||||||
:reporter,
|
:reporter,
|
||||||
:reviewed_by,
|
:reviewed_by,
|
||||||
request: :requester,
|
request: :requester,
|
||||||
assignment: :helper,
|
assignment: [:helper, :request],
|
||||||
message: :sender,
|
message: [:sender, assignment: :request],
|
||||||
activity: :creator,
|
activity: :creator,
|
||||||
activity_message: :sender
|
activity_message: [:sender, :activity]
|
||||||
])
|
])
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Pagination.page(limit, &{&1.inserted_at, &1.id})
|
|> Pagination.page(limit, &{&1.inserted_at, &1.id})
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,38 @@ defmodule WhoNeedHelpWeb.ModerationLive do
|
||||||
defp signal_label(:manual), do: gettext("Manual review")
|
defp signal_label(:manual), do: gettext("Manual review")
|
||||||
defp signal_label(value), do: to_string(value)
|
defp signal_label(value), do: to_string(value)
|
||||||
|
|
||||||
|
defp report_request(%{request: %WhoNeedHelp.Help.HelpRequest{} = request}), do: request
|
||||||
|
|
||||||
|
defp report_request(%{
|
||||||
|
assignment: %WhoNeedHelp.Help.Assignment{
|
||||||
|
request: %WhoNeedHelp.Help.HelpRequest{} = request
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
do: request
|
||||||
|
|
||||||
|
defp report_request(%{
|
||||||
|
message: %WhoNeedHelp.Messaging.Message{
|
||||||
|
assignment: %WhoNeedHelp.Help.Assignment{
|
||||||
|
request: %WhoNeedHelp.Help.HelpRequest{} = request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
do: request
|
||||||
|
|
||||||
|
defp report_request(_report), do: nil
|
||||||
|
|
||||||
|
defp report_activity(%{activity: %WhoNeedHelp.Activities.Activity{} = activity}),
|
||||||
|
do: activity
|
||||||
|
|
||||||
|
defp report_activity(%{
|
||||||
|
activity_message: %WhoNeedHelp.Activities.Message{
|
||||||
|
activity: %WhoNeedHelp.Activities.Activity{} = activity
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
do: activity
|
||||||
|
|
||||||
|
defp report_activity(_report), do: nil
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
|
|
@ -309,6 +341,8 @@ defmodule WhoNeedHelpWeb.ModerationLive do
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<article :for={report <- @reports} class="rounded-2xl border border-base-300 p-5">
|
<article :for={report <- @reports} class="rounded-2xl border border-base-300 p-5">
|
||||||
<% hide_form = scoped_form(%{"note" => ""}, :moderation, "report-hide-#{report.id}") %>
|
<% hide_form = scoped_form(%{"note" => ""}, :moderation, "report-hide-#{report.id}") %>
|
||||||
|
<% reported_request = report_request(report) %>
|
||||||
|
<% reported_activity = report_activity(report) %>
|
||||||
<% report_form =
|
<% report_form =
|
||||||
scoped_form(
|
scoped_form(
|
||||||
%{"status" => to_string(report.status), "resolution_note" => ""},
|
%{"status" => to_string(report.status), "resolution_note" => ""},
|
||||||
|
|
@ -324,6 +358,12 @@ defmodule WhoNeedHelpWeb.ModerationLive do
|
||||||
<span :if={report.activity_message_id} class="badge badge-info badge-outline">
|
<span :if={report.activity_message_id} class="badge badge-info badge-outline">
|
||||||
{gettext("activity message")}
|
{gettext("activity message")}
|
||||||
</span>
|
</span>
|
||||||
|
<span :if={report.message_id} class="badge badge-info badge-outline">
|
||||||
|
{gettext("matched chat message")}
|
||||||
|
</span>
|
||||||
|
<span :if={report.assignment_id} class="badge badge-info badge-outline">
|
||||||
|
{gettext("matched conversation")}
|
||||||
|
</span>
|
||||||
<span class="text-xs text-base-content/50">
|
<span class="text-xs text-base-content/50">
|
||||||
{gettext("by %{name}", name: report.reporter.display_name)}
|
{gettext("by %{name}", name: report.reporter.display_name)}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -338,29 +378,55 @@ defmodule WhoNeedHelpWeb.ModerationLive do
|
||||||
{gettext("View scoped evidence")}
|
{gettext("View scoped evidence")}
|
||||||
</button>
|
</button>
|
||||||
<.form
|
<.form
|
||||||
:if={report.request_id}
|
:if={reported_request && is_nil(reported_request.hidden_at)}
|
||||||
for={hide_form}
|
for={hide_form}
|
||||||
phx-submit="hide-request"
|
phx-submit="hide-request"
|
||||||
phx-value-id={report.request_id}
|
phx-value-id={reported_request.id}
|
||||||
class="flex gap-2"
|
class="flex gap-2"
|
||||||
>
|
>
|
||||||
<.input field={hide_form[:note]} placeholder={gettext("Hide reason")} />
|
<.input
|
||||||
|
field={hide_form[:note]}
|
||||||
|
placeholder={gettext("Required reason for hiding")}
|
||||||
|
required
|
||||||
|
/>
|
||||||
<.button class="btn btn-sm btn-error self-end">
|
<.button class="btn btn-sm btn-error self-end">
|
||||||
{gettext("Hide request")}
|
{gettext("Hide request")}
|
||||||
</.button>
|
</.button>
|
||||||
</.form>
|
</.form>
|
||||||
|
<button
|
||||||
|
:if={reported_request && reported_request.hidden_at}
|
||||||
|
type="button"
|
||||||
|
phx-click="restore-request"
|
||||||
|
phx-value-id={reported_request.id}
|
||||||
|
class="btn btn-sm btn-outline"
|
||||||
|
>
|
||||||
|
{gettext("Restore request")}
|
||||||
|
</button>
|
||||||
<.form
|
<.form
|
||||||
:if={report.activity_id}
|
:if={reported_activity && is_nil(reported_activity.hidden_at)}
|
||||||
for={hide_form}
|
for={hide_form}
|
||||||
phx-submit="hide-activity"
|
phx-submit="hide-activity"
|
||||||
phx-value-id={report.activity_id}
|
phx-value-id={reported_activity.id}
|
||||||
class="flex gap-2"
|
class="flex gap-2"
|
||||||
>
|
>
|
||||||
<.input field={hide_form[:note]} placeholder={gettext("Hide reason")} />
|
<.input
|
||||||
|
field={hide_form[:note]}
|
||||||
|
placeholder={gettext("Required reason for hiding")}
|
||||||
|
required
|
||||||
|
/>
|
||||||
<.button class="btn btn-sm btn-error self-end">
|
<.button class="btn btn-sm btn-error self-end">
|
||||||
{gettext("Hide activity")}
|
{gettext("Hide activity")}
|
||||||
</.button>
|
</.button>
|
||||||
</.form>
|
</.form>
|
||||||
|
<button
|
||||||
|
:if={reported_activity && reported_activity.hidden_at}
|
||||||
|
type="button"
|
||||||
|
phx-click="restore-activity"
|
||||||
|
phx-value-id={reported_activity.id}
|
||||||
|
class="btn btn-sm btn-outline"
|
||||||
|
>
|
||||||
|
{gettext("Restore activity")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<.form
|
<.form
|
||||||
for={report_form}
|
for={report_form}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,21 @@ defmodule WhoNeedHelp.TrustSafetyTest do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "hiding a request requires a moderation reason", context do
|
||||||
|
moderator =
|
||||||
|
user_fixture(display_name: "Reason moderator")
|
||||||
|
|> Ecto.Changeset.change(role: :moderator)
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
{:ok, request} = Help.create_request(context.requester_scope, context.attrs)
|
||||||
|
|
||||||
|
assert {:error, %Ecto.Changeset{} = changeset} =
|
||||||
|
Trust.hide_request(user_scope_fixture(moderator), request.id, "")
|
||||||
|
|
||||||
|
assert "can't be blank" in errors_on(changeset).hidden_reason
|
||||||
|
refute Repo.get!(WhoNeedHelp.Help.HelpRequest, request.id).hidden_at
|
||||||
|
end
|
||||||
|
|
||||||
test "category moderation cannot cross help and activity taxonomies", context do
|
test "category moderation cannot cross help and activity taxonomies", context do
|
||||||
moderator =
|
moderator =
|
||||||
user_fixture(display_name: "Category moderator")
|
user_fixture(display_name: "Category moderator")
|
||||||
|
|
|
||||||
|
|
@ -1113,7 +1113,7 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
|
||||||
{:ok, request} = Help.create_request(user_scope_fixture(requester), attrs)
|
{:ok, request} = Help.create_request(user_scope_fixture(requester), attrs)
|
||||||
{:ok, assignment} = Help.accept_request(user_scope_fixture(helper), request.id)
|
{:ok, assignment} = Help.accept_request(user_scope_fixture(helper), request.id)
|
||||||
|
|
||||||
{:ok, _message} =
|
{:ok, message} =
|
||||||
Messaging.send_message(user_scope_fixture(helper), assignment, %{
|
Messaging.send_message(user_scope_fixture(helper), assignment, %{
|
||||||
"body" => "Evidence message"
|
"body" => "Evidence message"
|
||||||
})
|
})
|
||||||
|
|
@ -1122,7 +1122,7 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
|
||||||
Trust.report(user_scope_fixture(requester), %{
|
Trust.report(user_scope_fixture(requester), %{
|
||||||
"reason" => "harassment",
|
"reason" => "harassment",
|
||||||
"details" => "Please review the matched conversation.",
|
"details" => "Please review the matched conversation.",
|
||||||
"assignment_id" => assignment.id
|
"message_id" => message.id
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, _proposal} =
|
{:ok, _proposal} =
|
||||||
|
|
@ -1140,6 +1140,11 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
|
||||||
assert html =~ "Please review the matched conversation."
|
assert html =~ "Please review the matched conversation."
|
||||||
assert html =~ "Bicycle repair"
|
assert html =~ "Bicycle repair"
|
||||||
|
|
||||||
|
assert has_element?(
|
||||||
|
view,
|
||||||
|
"form[phx-submit='hide-request'][phx-value-id='#{request.id}']"
|
||||||
|
)
|
||||||
|
|
||||||
evidence_html =
|
evidence_html =
|
||||||
view
|
view
|
||||||
|> element("button[phx-click='view-evidence']")
|
|> element("button[phx-click='view-evidence']")
|
||||||
|
|
@ -1148,6 +1153,60 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
|
||||||
assert evidence_html =~ "Evidence message"
|
assert evidence_html =~ "Evidence message"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "message reports expose parent activity moderation and switch to restore", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
scope: participant_scope
|
||||||
|
} do
|
||||||
|
user |> Ecto.Changeset.change(role: :moderator) |> Repo.update!()
|
||||||
|
organizer = user_fixture(display_name: "Activity organizer")
|
||||||
|
Catalog.seed_defaults()
|
||||||
|
activity_category = Catalog.list_categories(:activity) |> List.first()
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
Activities.create_activity(
|
||||||
|
user_scope_fixture(organizer),
|
||||||
|
activity_attrs(activity_category)
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, join_request} = Activities.request_to_join(participant_scope, activity.id)
|
||||||
|
|
||||||
|
{:ok, _approved} =
|
||||||
|
Activities.approve_participant(user_scope_fixture(organizer), join_request.id)
|
||||||
|
|
||||||
|
{:ok, message} =
|
||||||
|
Activities.send_message(user_scope_fixture(organizer), activity.id, %{
|
||||||
|
"body" => "Reported activity message"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _report} =
|
||||||
|
Trust.report(participant_scope, %{
|
||||||
|
"reason" => "harassment",
|
||||||
|
"details" => "Please review this activity message.",
|
||||||
|
"activity_message_id" => message.id
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/moderation")
|
||||||
|
|
||||||
|
assert has_element?(
|
||||||
|
view,
|
||||||
|
"form[phx-submit='hide-activity'][phx-value-id='#{activity.id}']"
|
||||||
|
)
|
||||||
|
|
||||||
|
view
|
||||||
|
|> form("form[phx-submit='hide-activity'][phx-value-id='#{activity.id}']", %{
|
||||||
|
"moderation" => %{"note" => "Unsafe public activity details"}
|
||||||
|
})
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
assert has_element?(
|
||||||
|
view,
|
||||||
|
"button[phx-click='restore-activity'][phx-value-id='#{activity.id}']"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert Repo.get!(WhoNeedHelp.Activities.Activity, activity.id).hidden_at
|
||||||
|
end
|
||||||
|
|
||||||
test "abuse-signal pagination stays available without opening report evidence", %{
|
test "abuse-signal pagination stays available without opening report evidence", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
user: user
|
user: user
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user