feat(member): stream the overview with keyset infinite scroll instead of loading every member
This commit is contained in:
parent
b09cdf7f3a
commit
b79d7ac9ea
13 changed files with 1067 additions and 451 deletions
|
|
@ -306,7 +306,8 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
# asserted on internal state to preserve the original coverage of the callback.
|
||||
assigns = :sys.get_state(view.pid).socket.assigns
|
||||
assert assigns.query == "Friedrich"
|
||||
assert is_list(assigns.members)
|
||||
# Loaded members are held as a `%{id => member}` map (the restream lookup window).
|
||||
assert is_map(assigns.members)
|
||||
end
|
||||
|
||||
@tag :ui
|
||||
|
|
@ -1069,6 +1070,63 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
assert bcc =~ "scope1%40example.com"
|
||||
refute bcc =~ "scope2%40example.com"
|
||||
end
|
||||
|
||||
test "selecting a member does not read the DB for mailto recipients (deferred to open)",
|
||||
%{conn: conn, member1: member1} do
|
||||
conn = conn_with_oidc_user(conn)
|
||||
{:ok, view, _html} = live(conn, "/members")
|
||||
|
||||
# A checkbox toggle is a non-search interaction; it must not issue a member
|
||||
# read just to precompute a mailto link the user may never open. The
|
||||
# recipient list is fetched lazily when the bulk-actions dropdown opens.
|
||||
member_reads =
|
||||
capture_member_select_queries(fn ->
|
||||
render_click(view, "select_member", %{"id" => member1.id})
|
||||
end)
|
||||
|
||||
assert member_reads == [],
|
||||
"select_member must not read members for the mailto recipients; got: #{inspect(member_reads)}"
|
||||
|
||||
# Opening the dropdown still surfaces the correct recipients for the selection.
|
||||
bcc = mailto_bcc(view)
|
||||
assert bcc =~ "scope1%40example.com"
|
||||
refute bcc =~ "scope2%40example.com"
|
||||
end
|
||||
|
||||
# Captures every SELECT against the members table emitted while `fun` runs.
|
||||
defp capture_member_select_queries(fun) do
|
||||
test_pid = self()
|
||||
handler_id = "test-member-select-#{System.unique_integer([:positive])}"
|
||||
|
||||
:telemetry.attach(
|
||||
handler_id,
|
||||
[:mv, :repo, :query],
|
||||
fn _event, _measurements, metadata, _config ->
|
||||
sql = metadata[:query] || ""
|
||||
|
||||
if String.contains?(sql, "SELECT") and String.contains?(sql, "\"members\"") do
|
||||
send(test_pid, {:member_query, sql})
|
||||
end
|
||||
end,
|
||||
nil
|
||||
)
|
||||
|
||||
try do
|
||||
fun.()
|
||||
after
|
||||
:telemetry.detach(handler_id)
|
||||
end
|
||||
|
||||
collect_member_queries([])
|
||||
end
|
||||
|
||||
defp collect_member_queries(acc) do
|
||||
receive do
|
||||
{:member_query, sql} -> collect_member_queries([sql | acc])
|
||||
after
|
||||
0 -> Enum.reverse(acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "cycle status filter" do
|
||||
|
|
@ -2372,12 +2430,12 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
# Should complete in less than 1 second (1000ms)
|
||||
assert duration < 1000, "Filter took #{duration}ms, expected < 1000ms"
|
||||
|
||||
# Verify filtering worked correctly - should show all true members
|
||||
Enum.each(1..75, fn i ->
|
||||
assert html =~ "TrueMember#{i}"
|
||||
end)
|
||||
# The overview now keyset-paginates: mount loads only the first page, not
|
||||
# the whole filtered set (§1.7). The filter still resolves DB-side, so the
|
||||
# loaded page contains only matching (true) members and never a non-matching
|
||||
# (false) one.
|
||||
assert html =~ "TrueMember"
|
||||
|
||||
# Should not show false members
|
||||
Enum.each(1..75, fn i ->
|
||||
refute html =~ "FalseMember#{i}"
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue