Skip to content

Commit 3fce684

Browse files
vdegovethbar
andauthored
Permalink pour la validation IRVE (#5524)
Co-authored-by: Thibaut Barrère <thibaut.barrere@gmail.com>
1 parent 8812cef commit 3fce684

4 files changed

Lines changed: 97 additions & 23 deletions

File tree

apps/transport/lib/irve/validator/summary.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,20 @@ defmodule Transport.IRVE.Validator.Summary do
2222
:column_errors,
2323
:error_samples
2424
]
25+
26+
@doc """
27+
Rebuilds a `%Summary{}` from its persisted (JSONB, string-keyed) `result` map. `struct!/2`
28+
makes an unexpected top-level key raise rather than silently render as `nil`; `error_samples` rows
29+
are re-keyed to atoms too (fixed key set) so the template keeps plain dot access.
30+
"""
31+
def from_result(result) when is_map(result) do
32+
result
33+
|> atomize_keys()
34+
|> Map.update!(:error_samples, fn samples -> Enum.map(samples, &atomize_keys/1) end)
35+
|> then(&struct!(__MODULE__, &1))
36+
end
37+
38+
defp atomize_keys(map) do
39+
Map.new(map, fn {key, value} -> {String.to_existing_atom(key), value} end)
40+
end
2541
end

apps/transport/lib/transport_web/controllers/validation_controller.ex

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,12 @@ defmodule TransportWeb.ValidationController do
6060
end
6161
end
6262

63-
# IRVE statique validation doesn’t use an Oban job and doesn’t store anything
64-
# It’s just a display and forget validation, and it’s quick enough so that it can be done synchronously
65-
# This clause intercepts the validation instead of sending to Validata like other TableSchema validations
63+
# This clause intercepts IRVE statique validation instead of routing it to Validata like the other
64+
# TableSchema validations, running it synchronously on the web server (which enables the permalink below)
65+
# rather than on a worker. The catch: it takes an order of magnitude more RAM than the raw CSV (at time of
66+
# writing), and the web node should stay isolated from that pressure to keep serving requests, so it would
67+
# be a good idea not to reproduce this pattern:
68+
# https://github.com/etalab/transport-site/pull/5524#pullrequestreview-4407262217
6669
def validate(%Plug.Conn{} = conn, %{
6770
"upload" => %{"file" => %{path: file_path, filename: filename}, "type" => "etalab/schema-irve-statique"}
6871
}) do
@@ -112,10 +115,18 @@ defmodule TransportWeb.ValidationController do
112115
defp validate_irve_statique(conn, file_path, filename, _size) do
113116
summary = Transport.IRVE.Validator.validate_and_summarize(file_path, irve_extension(filename))
114117

115-
conn
116-
|> assign(:summary, summary)
117-
|> assign(:filename, filename || "upload.csv")
118-
|> render("show_irve_statique.html")
118+
validation =
119+
%MultiValidation{
120+
validator: "on-demand-irve-statique",
121+
validation_timestamp: DateTime.utc_now(),
122+
# The #show route patterns match on secret_url_token
123+
oban_args: %{"type" => "irve-statique", "state" => "completed", "secret_url_token" => Ecto.UUID.generate()},
124+
result: Map.from_struct(summary),
125+
validated_data_name: filename || "upload.csv"
126+
}
127+
|> Repo.insert!()
128+
129+
redirect_to_validation_show(conn, validation)
119130
end
120131

121132
defp irve_extension(filename) when is_binary(filename) and filename != "" do
@@ -157,11 +168,7 @@ defmodule TransportWeb.ValidationController do
157168
validator = Transport.Validators.GTFSTransport
158169
current_issues = validator.get_issues(validation.result, params)
159170

160-
issue_type =
161-
case params["issue_type"] do
162-
nil -> validator.issue_type(current_issues)
163-
issue_type -> issue_type
164-
end
171+
issue_type = params["issue_type"] || validator.issue_type(current_issues)
165172

166173
conn
167174
|> assign_base_validation_details(params)
@@ -201,6 +208,12 @@ defmodule TransportWeb.ValidationController do
201208
|> assign(:xsd_errors, xsd_errors)
202209
|> render(template)
203210

211+
%MultiValidation{oban_args: %{"state" => "completed", "type" => "irve-statique"}} = validation ->
212+
conn
213+
|> assign(:summary, Transport.IRVE.Validator.Summary.from_result(validation.result))
214+
|> assign(:filename, validation.validated_data_name)
215+
|> render("show_irve_statique.html")
216+
204217
# Handles waiting for validation to complete, errors and
205218
# validation for schemas
206219
_ ->

apps/transport/lib/transport_web/templates/validation/show_irve_statique.html.heex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
<div class="container">
33
<div class="validation-title">
44
<h2>{dgettext("validations", "IRVE Statique validation report")}</h2>
5-
<p>
6-
{dgettext("validations", "Validation file")}: <b>{@filename}</b>
7-
</p>
85
<p class="notification">
96
{dgettext("validations", "This validation now uses our integrated IRVE Statique validator.")}
107
{" "}
@@ -16,6 +13,16 @@
1613
)
1714
)}
1815
</p>
16+
<p>
17+
{dgettext("validations", "Validation file")}: <b>{@filename}</b>
18+
</p>
19+
<p>
20+
{dgettext("validations", "This report can be shared with")}
21+
{link(dgettext("validations", "this permanent link"), to: current_url(@conn))}
22+
{dgettext("validations", "during %{retention_days} days",
23+
retention_days: Transport.Jobs.CleanOnDemandValidationJob.retention_days()
24+
)}.
25+
</p>
1926
</div>
2027
</div>
2128

apps/transport/test/transport_web/controllers/validation_controller_test.exs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,18 @@ defmodule TransportWeb.ValidationControllerTest do
253253
}
254254
})
255255

256-
response = html_response(conn, 200)
257-
assert response =~ "icon--validation\">✅</span>"
258-
assert response =~ "validata.fr/table-schema"
256+
assert 1 == count_validations()
259257

260-
assert 0 == count_validations()
258+
assert %{
259+
oban_args: %{"state" => "completed", "type" => "irve-statique", "secret_url_token" => token},
260+
result: %{"valid" => true},
261+
validator: "on-demand-irve-statique",
262+
validated_data_name: "irve.csv",
263+
id: validation_id
264+
} = DB.MultiValidation.with_result() |> DB.Repo.one!()
265+
266+
assert [] == all_enqueued(worker: Transport.Jobs.OnDemandValidationJob, queue: :on_demand_validation)
267+
assert redirected_to(conn, 302) == validation_path(conn, :show, validation_id, token: token)
261268

262269
assert [
263270
%DB.FeatureUsage{
@@ -284,11 +291,14 @@ defmodule TransportWeb.ValidationControllerTest do
284291
}
285292
})
286293

287-
response = html_response(conn, 200)
288-
assert response =~ "icon--validation\">❌</span>"
289-
assert response =~ "puissance_nominale"
294+
assert 1 == count_validations()
290295

291-
assert 0 == count_validations()
296+
assert %{
297+
oban_args: %{"state" => "completed", "type" => "irve-statique"},
298+
result: %{"valid" => false}
299+
} = DB.MultiValidation.with_result() |> DB.Repo.one!()
300+
301+
assert redirected_to(conn, 302) =~ "/validation/"
292302
end)
293303
end
294304

@@ -810,6 +820,34 @@ defmodule TransportWeb.ValidationControllerTest do
810820
assert render(view) =~ "Value is not allowed in enum."
811821
end
812822

823+
test "with a completed IRVE Statique validation", %{conn: conn} do
824+
token = Ecto.UUID.generate()
825+
826+
mv =
827+
insert(:multi_validation,
828+
oban_args: %{
829+
"state" => "completed",
830+
"type" => "irve-statique",
831+
"secret_url_token" => token
832+
},
833+
result: %{
834+
"valid" => true,
835+
"valid_row_count" => 1,
836+
"invalid_row_count" => 0,
837+
"total_row_count" => 1,
838+
"file_level_errors" => [],
839+
"column_errors" => %{},
840+
"error_samples" => []
841+
},
842+
validated_data_name: "irve.csv"
843+
)
844+
845+
response = conn |> get(validation_path(conn, :show, mv.id, token: token)) |> html_response(200)
846+
assert response =~ "icon--validation\">✅</span>"
847+
assert response =~ "validata.fr/table-schema"
848+
assert response =~ "irve.csv"
849+
end
850+
813851
test "with a GTFS-RT validation", %{conn: conn} do
814852
gtfs_rt_url = "https://example.com/gtfs-rt"
815853

0 commit comments

Comments
 (0)