refactor
This commit is contained in:
parent
04b0916c1e
commit
bf7e47ce5c
7 changed files with 147 additions and 126 deletions
|
|
@ -48,6 +48,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
alias Mv.Membership
|
||||
alias Mv.Membership.Import.MemberCSV
|
||||
alias MvWeb.Authorization
|
||||
alias Mv.Config
|
||||
|
||||
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
||||
|
||||
|
|
@ -140,14 +141,6 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
"Use the custom field name as the CSV column header (same normalization as member fields applies)"
|
||||
)}
|
||||
</p>
|
||||
<div class="mt-2">
|
||||
<.link
|
||||
navigate={~p"/custom_field_values"}
|
||||
class="link link-primary"
|
||||
>
|
||||
{gettext("Manage Custom Fields")}
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -209,7 +202,8 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
phx-disable-with={gettext("Starting import...")}
|
||||
variant="primary"
|
||||
disabled={
|
||||
Enum.empty?(@uploads.csv_file.entries) or
|
||||
@import_status == :running or
|
||||
Enum.empty?(@uploads.csv_file.entries) or
|
||||
@uploads.csv_file.entries |> List.first() |> then(&(&1 && not &1.done?))
|
||||
}
|
||||
data-testid="start-import-button"
|
||||
|
|
@ -226,7 +220,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
class="mt-4"
|
||||
data-testid="import-progress-container"
|
||||
>
|
||||
<%= if @import_status == :running do %>
|
||||
<%= if @import_progress.status == :running do %>
|
||||
<p class="text-sm" data-testid="import-progress-text">
|
||||
{gettext("Processing chunk %{current} of %{total}...",
|
||||
current: @import_progress.current_chunk,
|
||||
|
|
@ -235,7 +229,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= if @import_status == :done do %>
|
||||
<%= if @import_progress.status == :done do %>
|
||||
<section class="space-y-4" data-testid="import-results-panel">
|
||||
<h2 class="text-lg font-semibold">
|
||||
{gettext("Import Results")}
|
||||
|
|
@ -372,51 +366,85 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
def handle_event("start_import", _params, socket) do
|
||||
# Server-side admin check
|
||||
if Authorization.can?(socket.assigns[:current_user], :create, Mv.Membership.Member) do
|
||||
# Check if upload is completed
|
||||
upload_entries = socket.assigns.uploads.csv_file.entries
|
||||
|
||||
if Enum.empty?(upload_entries) do
|
||||
# Prevent concurrent imports
|
||||
if socket.assigns.import_status == :running do
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Please select a CSV file to import.")
|
||||
gettext("Import is already running. Please wait for it to complete.")
|
||||
)}
|
||||
else
|
||||
entry = List.first(upload_entries)
|
||||
# Check if upload is completed
|
||||
upload_entries = socket.assigns.uploads.csv_file.entries
|
||||
|
||||
if entry.done? do
|
||||
with {:ok, content} <- consume_and_read_csv(socket) do
|
||||
case MemberCSV.prepare(content) do
|
||||
{:ok, import_state} ->
|
||||
total_chunks = length(import_state.chunks)
|
||||
if Enum.empty?(upload_entries) do
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Please select a CSV file to import.")
|
||||
)}
|
||||
else
|
||||
entry = List.first(upload_entries)
|
||||
|
||||
progress = %{
|
||||
inserted: 0,
|
||||
failed: 0,
|
||||
errors: [],
|
||||
warnings: import_state.warnings || [],
|
||||
status: :running,
|
||||
current_chunk: 0,
|
||||
total_chunks: total_chunks
|
||||
}
|
||||
if entry.done? do
|
||||
with {:ok, content} <- consume_and_read_csv(socket) do
|
||||
case MemberCSV.prepare(content) do
|
||||
{:ok, import_state} ->
|
||||
total_chunks = length(import_state.chunks)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:import_state, import_state)
|
||||
|> assign(:import_progress, progress)
|
||||
|> assign(:import_status, :running)
|
||||
progress = %{
|
||||
inserted: 0,
|
||||
failed: 0,
|
||||
errors: [],
|
||||
warnings: import_state.warnings || [],
|
||||
status: :running,
|
||||
current_chunk: 0,
|
||||
total_chunks: total_chunks,
|
||||
errors_truncated?: false
|
||||
}
|
||||
|
||||
send(self(), {:process_chunk, 0})
|
||||
socket =
|
||||
socket
|
||||
|> assign(:import_state, import_state)
|
||||
|> assign(:import_progress, progress)
|
||||
|> assign(:import_status, :running)
|
||||
|
||||
{:noreply, socket}
|
||||
send(self(), {:process_chunk, 0})
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
{:error, error} ->
|
||||
error_message =
|
||||
case error do
|
||||
%{message: msg} when is_binary(msg) -> msg
|
||||
%{errors: errors} when is_list(errors) -> inspect(errors)
|
||||
reason when is_binary(reason) -> reason
|
||||
other -> inspect(other)
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Failed to prepare CSV import: %{error}", error: error_message)
|
||||
)}
|
||||
end
|
||||
else
|
||||
{:error, reason} when is_binary(reason) ->
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Failed to prepare CSV import: %{reason}", reason: reason)
|
||||
)}
|
||||
|
||||
{:error, error} ->
|
||||
error_message =
|
||||
case error do
|
||||
%{message: msg} when is_binary(msg) -> msg
|
||||
%{errors: errors} when is_list(errors) -> inspect(errors)
|
||||
reason when is_binary(reason) -> reason
|
||||
other -> inspect(other)
|
||||
end
|
||||
|
||||
|
|
@ -428,36 +456,13 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
)}
|
||||
end
|
||||
else
|
||||
{:error, reason} when is_binary(reason) ->
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Failed to prepare CSV import: %{reason}", reason: reason)
|
||||
)}
|
||||
|
||||
{:error, error} ->
|
||||
error_message =
|
||||
case error do
|
||||
%{message: msg} when is_binary(msg) -> msg
|
||||
%{errors: errors} when is_list(errors) -> inspect(errors)
|
||||
other -> inspect(other)
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Failed to prepare CSV import: %{error}", error: error_message)
|
||||
)}
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Please wait for the file upload to complete before starting the import.")
|
||||
)}
|
||||
end
|
||||
else
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
:error,
|
||||
gettext("Please wait for the file upload to complete before starting the import.")
|
||||
)}
|
||||
end
|
||||
end
|
||||
else
|
||||
|
|
@ -576,6 +581,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
end
|
||||
|
||||
# Starts async task to process a chunk
|
||||
# In tests (SQL sandbox mode), runs synchronously to avoid Ecto Sandbox issues
|
||||
defp start_chunk_processing_task(socket, import_state, progress, idx) do
|
||||
chunk = Enum.at(import_state.chunks, idx)
|
||||
actor = socket.assigns[:current_user]
|
||||
|
|
@ -589,21 +595,33 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
actor: actor
|
||||
]
|
||||
|
||||
# Start async task to process chunk
|
||||
Task.Supervisor.async_nolink(Mv.TaskSupervisor, fn ->
|
||||
case MemberCSV.process_chunk(
|
||||
chunk,
|
||||
import_state.column_map,
|
||||
import_state.custom_field_map,
|
||||
opts
|
||||
) do
|
||||
{:ok, chunk_result} ->
|
||||
send(live_view_pid, {:chunk_done, idx, chunk_result})
|
||||
if Config.sql_sandbox?() do
|
||||
# Run synchronously in tests to avoid Ecto Sandbox issues with async tasks
|
||||
{:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(
|
||||
chunk,
|
||||
import_state.column_map,
|
||||
import_state.custom_field_map,
|
||||
opts
|
||||
)
|
||||
|
||||
{:error, reason} ->
|
||||
send(live_view_pid, {:chunk_error, idx, reason})
|
||||
end
|
||||
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
|
||||
send(live_view_pid, {:chunk_done, idx, chunk_result})
|
||||
else
|
||||
# Start async task to process chunk in production
|
||||
Task.Supervisor.async_nolink(Mv.TaskSupervisor, fn ->
|
||||
{:ok, chunk_result} =
|
||||
MemberCSV.process_chunk(
|
||||
chunk,
|
||||
import_state.column_map,
|
||||
import_state.custom_field_map,
|
||||
opts
|
||||
)
|
||||
|
||||
send(live_view_pid, {:chunk_done, idx, chunk_result})
|
||||
end)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
|
@ -670,10 +688,10 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
end
|
||||
end)
|
||||
|> case do
|
||||
[{_name, {:ok, content}}] when is_binary(content) ->
|
||||
[{:ok, content}] when is_binary(content) ->
|
||||
{:ok, content}
|
||||
|
||||
[{_name, {:error, reason}}] ->
|
||||
[{:error, reason}] ->
|
||||
{:error, gettext("Failed to read file: %{reason}", reason: reason)}
|
||||
|
||||
[] ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue