diff --git a/lib/arrow/trainsformer/export.ex b/lib/arrow/trainsformer/export.ex
index 1e3d124d1..0f7d51fa5 100644
--- a/lib/arrow/trainsformer/export.ex
+++ b/lib/arrow/trainsformer/export.ex
@@ -26,8 +26,16 @@ defmodule Arrow.Trainsformer.Export do
def changeset(export, attrs) do
export
|> cast(attrs, [:s3_path, :disruption_id, :name])
- |> cast_assoc(:services, with: &Service.changeset/2, required: true)
- |> cast_assoc(:routes, with: &Route.changeset/2, required: true)
+ |> cast_assoc(:services,
+ with: &Service.changeset/2,
+ required: true,
+ required_message: "Export must contain at least one Service ID"
+ )
+ |> cast_assoc(:routes,
+ with: &Route.changeset/2,
+ required: true,
+ required_message: "Export must contain at least one route"
+ )
|> validate_required([:s3_path])
|> assoc_constraint(:disruption)
end
diff --git a/lib/arrow_web/components/core_components.ex b/lib/arrow_web/components/core_components.ex
index 37e9a98a6..d4bf84459 100644
--- a/lib/arrow_web/components/core_components.ex
+++ b/lib/arrow_web/components/core_components.ex
@@ -452,6 +452,37 @@ defmodule ArrowWeb.CoreComponents do
"""
end
+ @doc """
+ Utility for showing errors on a `Phoenix.HTML.Form`
+
+ Takes a form field as `field` and renders [`<.error>`](`error/1`) for each error
+
+ <.errors field={@form[:services]} />
+
+ Can specify `always_show` to always show errors regardless of if the
+ field is considered "used" by `Phoenix.Component`
+
+ <.errors field={@form[:services]} always_show />
+ """
+ attr :field, Phoenix.HTML.FormField,
+ doc: "a form field struct retrieved from the form, for example: `@form[:email]`"
+
+ attr :always_show, :boolean,
+ default: false,
+ doc: "a flag that forces any errors to be displayed regardless of it's used state"
+
+ def errors(%{field: field, always_show: show?} = assigns) do
+ errors =
+ if(Phoenix.Component.used_input?(field) or show?, do: field.errors, else: [])
+
+ assigns =
+ assign(assigns, errors: Enum.map(errors, &translate_error(&1)))
+
+ ~H"""
+ <.error :for={msg <- @errors} class="d-block alert alert-danger">{msg}
+ """
+ end
+
def custom_normalize_value("text", value) when is_map(value) do
iodata = Jason.encode_to_iodata!(value)
Phoenix.HTML.Form.normalize_value("text", iodata)
diff --git a/lib/arrow_web/components/edit_trainsformer_export_form.ex b/lib/arrow_web/components/edit_trainsformer_export_form.ex
index fde05df77..32f6097b7 100644
--- a/lib/arrow_web/components/edit_trainsformer_export_form.ex
+++ b/lib/arrow_web/components/edit_trainsformer_export_form.ex
@@ -141,6 +141,7 @@ defmodule ArrowWeb.EditTrainsformerExportForm do
<% end %>
+ <.errors field={@form[:routes]} always_show />
Service ID
@@ -250,6 +251,7 @@ defmodule ArrowWeb.EditTrainsformerExportForm do
+ <.errors field={@form[:services]} always_show />
@@ -537,35 +539,21 @@ defmodule ArrowWeb.EditTrainsformerExportForm do
end
defp update_export(export_params, socket) do
- imported_services =
- for {key, value} <- export_params["services"],
- into: %{},
- do: {key, value}
+ export = Trainsformer.get_export!(socket.assigns.export.id)
- if imported_services == %{} do
- {:noreply, assign(socket, error: "You must import at least one service")}
- else
- export = Trainsformer.get_export!(socket.assigns.export.id)
-
- case Trainsformer.update_export(export, export_params) do
- {:ok, _} ->
- {:noreply,
- socket
- |> push_patch(to: "/disruptions/#{socket.assigns.disruption.id}")
- |> put_flash(:info, "Trainsformer service schedules updated successfully!")}
-
- {:error, %Ecto.Changeset{} = changeset} ->
- {:noreply, assign(socket, form: to_form(changeset))}
- end
+ case Trainsformer.update_export(export, export_params) do
+ {:ok, _} ->
+ {:noreply,
+ socket
+ |> push_patch(to: "/disruptions/#{socket.assigns.disruption.id}")
+ |> put_flash(:info, "Trainsformer service schedules updated successfully!")}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, form: to_form(changeset))}
end
end
defp create_export(export_params, socket) do
- imported_services =
- for {key, value} <- export_params["services"],
- into: %{},
- do: {key, value}
-
export_params = Map.put(export_params, "routes", socket.assigns.uploaded_file_routes)
with {:ok, s3_path} <-
@@ -578,8 +566,7 @@ defmodule ArrowWeb.EditTrainsformerExportForm do
Trainsformer.create_export(%{
export_params
| "s3_path" => s3_path,
- "name" => socket.assigns.uploaded_file_name,
- "services" => imported_services
+ "name" => socket.assigns.uploaded_file_name
}) do
{:noreply,
socket
diff --git a/test/integration/disruptionsv2/trainsformer_export_section_test.exs b/test/integration/disruptionsv2/trainsformer_export_section_test.exs
index 48c9e4ac1..466f20206 100644
--- a/test/integration/disruptionsv2/trainsformer_export_section_test.exs
+++ b/test/integration/disruptionsv2/trainsformer_export_section_test.exs
@@ -217,6 +217,28 @@ defmodule Arrow.Integration.Disruptionsv2.TrainsformerExportSectionTest do
)
end
+ feature "reports errors about missing service ids and route ids", %{session: session} do
+ disruption = disruption_v2_fixture(%{mode: :commuter_rail})
+
+ session
+ |> visit("/disruptions/#{disruption.id}")
+ |> click(text("Upload Trainsformer export"))
+ |> assert_text("Upload Trainsformer .zip")
+ |> attach_file(file_field("trainsformer_export", visible: false),
+ path:
+ "test/support/fixtures/trainsformer/invalid,reasons=no-trips,no-stop-times,no-multi-route-trips.zip"
+ )
+ |> assert_text(
+ "Successfully imported export invalid,reasons=no-trips,no-stop-times,no-multi-route-trips.zip!"
+ )
+ |> assert_text("Export must contain at least one Service ID")
+ |> assert_text("Export must contain at least one route")
+ |> click(Query.css("#save-export-button"))
+ |> assert_text("Export must contain at least one Service ID")
+ |> assert_text("Export must contain at least one route")
+ |> assert_has(Query.css("#save-export-button"))
+ end
+
feature "can cancel uploading a Trainsformer export", %{session: session} do
disruption = disruption_v2_fixture(%{mode: :commuter_rail})
diff --git a/test/support/fixtures/trainsformer/invalid,reasons=no-trips,no-stop-times,no-multi-route-trips.zip b/test/support/fixtures/trainsformer/invalid,reasons=no-trips,no-stop-times,no-multi-route-trips.zip
new file mode 100644
index 000000000..b5693855a
Binary files /dev/null and b/test/support/fixtures/trainsformer/invalid,reasons=no-trips,no-stop-times,no-multi-route-trips.zip differ