refactor
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing

This commit is contained in:
carla 2026-02-04 15:52:00 +01:00
parent e0f0ca369c
commit d34ff57531
6 changed files with 127 additions and 391 deletions

View file

@ -45,6 +45,9 @@ defmodule MvWeb.ImportExportLive do
# after this limit is reached.
@max_errors 50
# Maximum length for error messages before truncation
@max_error_message_length 200
@impl true
def mount(_params, session, socket) do
# Get locale from session for translations
@ -95,11 +98,11 @@ defmodule MvWeb.ImportExportLive do
<%= if Authorization.can?(@current_user, :create, Mv.Membership.Member) do %>
<%!-- CSV Import Section --%>
<.form_section title={gettext("Import Members (CSV)")}>
<%= import_info_box(assigns) %>
<%= template_links(assigns) %>
<%= import_form(assigns) %>
{import_info_box(assigns)}
{template_links(assigns)}
{import_form(assigns)}
<%= if @import_status == :running or @import_status == :done do %>
<%= import_progress(assigns) %>
{import_progress(assigns)}
<% end %>
</.form_section>
@ -243,7 +246,7 @@ defmodule MvWeb.ImportExportLive do
<% end %>
<%= if @import_progress.status == :done do %>
<%= import_results(assigns) %>
{import_results(assigns)}
<% end %>
</div>
<% end %>
@ -487,9 +490,7 @@ defmodule MvWeb.ImportExportLive do
# Formats Ash validation errors for display
defp format_ash_error(%Ash.Error.Invalid{errors: errors}) when is_list(errors) do
errors
|> Enum.map(&format_single_error/1)
|> Enum.join(", ")
Enum.map_join(errors, ", ", &format_single_error/1)
end
defp format_ash_error(error) do
@ -498,9 +499,7 @@ defmodule MvWeb.ImportExportLive do
# Formats a list of errors into a readable string
defp format_error_list(errors) do
errors
|> Enum.map(&format_single_error/1)
|> Enum.join(", ")
Enum.map_join(errors, ", ", &format_single_error/1)
end
# Formats a single error item
@ -516,8 +515,8 @@ defmodule MvWeb.ImportExportLive do
defp format_unknown_error(other) do
error_str = inspect(other, limit: :infinity, pretty: true)
if String.length(error_str) > 200 do
String.slice(error_str, 0, 197) <> "..."
if String.length(error_str) > @max_error_message_length do
String.slice(error_str, 0, @max_error_message_length - 3) <> "..."
else
error_str
end
@ -558,6 +557,49 @@ defmodule MvWeb.ImportExportLive do
handle_chunk_error(socket, :processing_failed, idx, reason)
end
# Processes a chunk with error handling and sends result message to LiveView.
#
# Handles errors from MemberCSV.process_chunk and sends appropriate messages
# to the LiveView process for progress tracking.
@spec process_chunk_with_error_handling(
list(),
map(),
map(),
keyword(),
pid(),
non_neg_integer()
) :: :ok
defp process_chunk_with_error_handling(
chunk,
column_map,
custom_field_map,
opts,
live_view_pid,
idx
) do
result =
try do
MemberCSV.process_chunk(chunk, column_map, custom_field_map, opts)
rescue
e ->
{:error, Exception.message(e)}
catch
:exit, reason ->
{:error, inspect(reason)}
:throw, reason ->
{:error, inspect(reason)}
end
case result do
{:ok, chunk_result} ->
send(live_view_pid, {:chunk_done, idx, chunk_result})
{:error, reason} ->
send(live_view_pid, {:chunk_error, idx, reason})
end
end
# Starts async task to process a chunk of CSV rows.
#
# In tests (SQL sandbox mode), runs synchronously to avoid Ecto Sandbox issues.
@ -586,33 +628,16 @@ defmodule MvWeb.ImportExportLive do
if Config.sql_sandbox?() do
# Run synchronously in tests to avoid Ecto Sandbox issues with async tasks
result =
try do
MemberCSV.process_chunk(
chunk,
import_state.column_map,
import_state.custom_field_map,
opts
)
rescue
e ->
{:error, Exception.message(e)}
catch
:exit, reason ->
{:error, inspect(reason)}
:throw, reason ->
{:error, inspect(reason)}
end
case result do
{:ok, chunk_result} ->
# In test mode, send the message - it will be processed when render() is called
# in the test. The test helper wait_for_import_completion() handles message processing
send(live_view_pid, {:chunk_done, idx, chunk_result})
{:error, reason} ->
send(live_view_pid, {:chunk_error, idx, reason})
end
# In test mode, send the message - it will be processed when render() is called
# in the test. The test helper wait_for_import_completion() handles message processing
process_chunk_with_error_handling(
chunk,
import_state.column_map,
import_state.custom_field_map,
opts,
live_view_pid,
idx
)
else
# Start async task to process chunk in production
# Use start_child for fire-and-forget: no monitor, no Task messages
@ -621,31 +646,14 @@ defmodule MvWeb.ImportExportLive do
# Set locale in task process for translations
Gettext.put_locale(MvWeb.Gettext, locale)
result =
try do
MemberCSV.process_chunk(
chunk,
import_state.column_map,
import_state.custom_field_map,
opts
)
rescue
e ->
{:error, Exception.message(e)}
catch
:exit, reason ->
{:error, inspect(reason)}
:throw, reason ->
{:error, inspect(reason)}
end
case result do
{:ok, chunk_result} ->
send(live_view_pid, {:chunk_done, idx, chunk_result})
{:error, reason} ->
send(live_view_pid, {:chunk_error, idx, reason})
end
process_chunk_with_error_handling(
chunk,
import_state.column_map,
import_state.custom_field_map,
opts,
live_view_pid,
idx
)
end)
end
@ -712,8 +720,14 @@ defmodule MvWeb.ImportExportLive do
@spec consume_and_read_csv(Phoenix.LiveView.Socket.t()) ::
{:ok, String.t()} | {:error, String.t()}
defp consume_and_read_csv(socket) do
case consume_uploaded_entries(socket, :csv_file, &read_file_entry/2) do
[{:ok, content}] ->
raw = consume_uploaded_entries(socket, :csv_file, &read_file_entry/2)
case raw do
[{:ok, content}] when is_binary(content) ->
{:ok, content}
# Phoenix LiveView test (render_upload) can return raw content list when callback return is treated as value
[content] when is_binary(content) ->
{:ok, content}
[{:error, reason}] ->