Skip to content

Commit 638c48b

Browse files
fchabouisthbar
andauthored
Le site n'utilise plus les geojson des ressources communautaires (#2020)
* add geojson file size information in payload * function to get a resource related geojson * same thing for a dataset * add assign for resource controller * add assign for dataset controller * update resource view existing functions accordingly * plug the new function for resources * use the new function for dataset details page * add a test * add condition on convert_to format * update test * format file * add nil case (can happen in tests) * don't match, assert equal * use the updated_at field of resource_history so we know when was the last check * update the tests * use that new information * update test * new function to convert seconds to hours and minutes * finally add the information to the webpage * mix format * credo + warning * simplify test * correct file_size computation * use a dedicated field for freshness information * valide_at should be filled when creating resource ! * change field name * correct merge issue Co-authored-by: Thibaut Barrère <[email protected]>
1 parent cfeb10f commit 638c48b

20 files changed

+222
-28
lines changed

apps/db/lib/db/dataset.ex

+9
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,15 @@ defmodule DB.Dataset do
601601
|> Repo.all()
602602
end
603603

604+
@spec get_resources_related_files(any()) :: map()
605+
def get_resources_related_files(%__MODULE__{resources: resources}) when is_list(resources) do
606+
resources
607+
|> Enum.map(fn %{id: id} = r -> {id, DB.Resource.get_related_files(r)} end)
608+
|> Enum.into(%{})
609+
end
610+
611+
def get_resources_related_files(_), do: %{}
612+
604613
@spec validate_territory_mutual_exclusion(Ecto.Changeset.t()) :: Ecto.Changeset.t()
605614
defp validate_territory_mutual_exclusion(changeset) do
606615
has_cities =

apps/db/lib/db/resource.ex

+35-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ defmodule DB.Resource do
5959
belongs_to(:dataset, Dataset)
6060
has_one(:validation, Validation, on_replace: :delete)
6161
has_many(:logs_validation, LogsValidation, on_replace: :delete, on_delete: :delete_all)
62-
has_many(:resource_unavailabilities, ResourceUnavailability, on_replace: :delete, on_delete: :delete_all)
62+
63+
has_many(:resource_unavailabilities, ResourceUnavailability,
64+
on_replace: :delete,
65+
on_delete: :delete_all
66+
)
6367
end
6468

6569
defp gtfs_validator, do: Shared.Validation.GtfsValidator.Wrapper.impl()
@@ -535,7 +539,10 @@ defmodule DB.Resource do
535539
end
536540

537541
@spec ttl(__MODULE__.t()) :: integer() | nil
538-
def ttl(%__MODULE__{format: "gbfs", metadata: %{"ttl" => ttl}}) when is_integer(ttl) and ttl >= 0, do: ttl
542+
def ttl(%__MODULE__{format: "gbfs", metadata: %{"ttl" => ttl}})
543+
when is_integer(ttl) and ttl >= 0,
544+
do: ttl
545+
539546
def ttl(_), do: nil
540547

541548
@spec can_direct_download?(__MODULE__.t()) :: boolean
@@ -582,4 +589,30 @@ defmodule DB.Resource do
582589
where: resource.id == ^id
583590
)
584591
end
592+
593+
@spec get_related_files(__MODULE__.t()) :: map()
594+
def get_related_files(%__MODULE__{datagouv_id: resource_datagouv_id}) do
595+
%{}
596+
|> Map.put(:geojson, get_related_geojson_info(resource_datagouv_id))
597+
end
598+
599+
@spec get_related_geojson_info(binary() | nil) :: %{url: binary(), filesize: binary()} | nil
600+
def get_related_geojson_info(nil), do: nil
601+
602+
def get_related_geojson_info(resource_datagouv_id) do
603+
DB.ResourceHistory
604+
|> join(:inner, [rh], dc in DB.DataConversion,
605+
as: :dc,
606+
on: fragment("?::text = ? ->> 'uuid'", dc.resource_history_uuid, rh.payload)
607+
)
608+
|> select([rh, dc], %{
609+
url: fragment("? ->> 'permanent_url'", dc.payload),
610+
filesize: fragment("? ->> 'filesize'", dc.payload),
611+
resource_history_last_up_to_date_at: rh.last_up_to_date_at
612+
})
613+
|> where([rh, dc], rh.datagouv_id == ^resource_datagouv_id and dc.convert_to == "GeoJSON")
614+
|> order_by([rh, _], desc: rh.inserted_at)
615+
|> limit(1)
616+
|> DB.Repo.one()
617+
end
585618
end

apps/db/lib/db/resource_history.ex

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ defmodule DB.ResourceHistory do
88
typed_schema "resource_history" do
99
field(:datagouv_id, :string)
1010
field(:payload, :map)
11+
# the last moment we checked and the resource history was corresponding to the real online resource
12+
field(:last_up_to_date_at, :utc_datetime_usec)
1113

1214
timestamps(type: :utc_datetime_usec)
1315
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule DB.Repo.Migrations.AddResourceHistoryLastUpToDateAtField do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:resource_history) do
6+
add(:last_up_to_date_at, :utc_datetime_usec)
7+
end
8+
end
9+
end

apps/db/test/resource_test.exs

+43
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,49 @@ defmodule DB.ResourceTest do
132132
assert reasons == %{"content hash has changed" => 1, "no previous validation" => 1}
133133
end
134134

135+
test "get resource related geojson infos" do
136+
now = DateTime.now!("Etc/UTC")
137+
138+
# we insert 3 resource history for datagouv_id_1
139+
insert_resouce_history("datagouv_id_1", uuid1 = Ecto.UUID.generate(), now, -3600)
140+
insert_resouce_history("datagouv_id_1", uuid2 = Ecto.UUID.generate(), now)
141+
insert_resouce_history("datagouv_id_1", uuid3 = Ecto.UUID.generate(), now, -3601)
142+
143+
# and one for datagouv_id_2
144+
insert_resouce_history("datagouv_id_2", uuid4 = Ecto.UUID.generate(), now)
145+
146+
# we insert 1 conversion for each resource history
147+
insert_data_conversion(uuid1, "url1", 10)
148+
insert_data_conversion(uuid2, "url2", 12)
149+
insert_data_conversion(uuid3, "url3", 10)
150+
insert_data_conversion(uuid4, "url4", 10)
151+
152+
assert %{url: "url2", filesize: "12", resource_history_last_up_to_date_at: _} =
153+
DB.Resource.get_related_geojson_info("datagouv_id_1")
154+
155+
assert nil == DB.Resource.get_related_geojson_info("other_id")
156+
157+
assert %{geojson: %{url: "url2", filesize: "12", resource_history_last_up_to_date_at: _}} =
158+
DB.Resource.get_related_files(%DB.Resource{datagouv_id: "datagouv_id_1"})
159+
end
160+
161+
defp insert_resouce_history(datagouv_id, uuid, datetime, time_delta_seconds \\ 0) do
162+
insert(:resource_history, %{
163+
datagouv_id: datagouv_id,
164+
payload: %{uuid: uuid},
165+
inserted_at: DateTime.add(datetime, time_delta_seconds, :second)
166+
})
167+
end
168+
169+
defp insert_data_conversion(uuid, permanent_url, filesize) do
170+
insert(:data_conversion, %{
171+
resource_history_uuid: uuid,
172+
convert_from: "GTFS",
173+
convert_to: "GeoJSON",
174+
payload: %{permanent_url: permanent_url, filesize: filesize}
175+
})
176+
end
177+
135178
test "needs validation with a JSON Schema" do
136179
schema_name = "etalab/foo"
137180
resource = insert(:resource, %{schema_name: schema_name})

apps/transport/lib/jobs/gtfs_to_geojson_converter_job.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,17 @@ defmodule Transport.Jobs.SingleGtfsToGeojsonConverterJob do
9292
geojson_file_name = resource_filename |> geojson_file_name()
9393
Transport.S3.upload_to_s3!(:history, file, geojson_file_name)
9494

95+
{:ok, %{size: filesize}} = File.stat(geojson_file_path)
96+
9597
%DataConversion{
9698
convert_from: "GTFS",
9799
convert_to: "GeoJSON",
98100
resource_history_uuid: resource_uuid,
99101
payload: %{
100102
filename: geojson_file_name,
101103
permanent_url: Transport.S3.permanent_url(:history, geojson_file_name),
102-
resource_datagouv_id: resource_datagouv_id
104+
resource_datagouv_id: resource_datagouv_id,
105+
filesize: filesize
103106
}
104107
}
105108
|> Repo.insert!()

apps/transport/lib/jobs/resource_history_job.ex

+16-3
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ defmodule Transport.Jobs.ResourceHistoryJob do
118118
Transport.S3.upload_to_s3!(:history, body, filename)
119119
store_resource_history!(resource, data)
120120

121-
false ->
121+
{false, history} ->
122122
# Good opportunity to add a :telemetry event
123123
Logger.debug("skipping historization for #{datagouv_id} because resource did not change")
124+
touch_resource_history!(history)
124125
end
125126
end
126127

@@ -142,7 +143,11 @@ defmodule Transport.Jobs.ResourceHistoryJob do
142143
|> limit(1)
143144
|> DB.Repo.one()
144145

145-
is_nil(history) or not is_same_resource?(history, zip_metadata)
146+
case {history, is_same_resource?(history, zip_metadata)} do
147+
{nil, _} -> true
148+
{_history, false} -> true
149+
{history, true} -> {false, history}
150+
end
146151
end
147152

148153
@doc """
@@ -154,15 +159,23 @@ defmodule Transport.Jobs.ResourceHistoryJob do
154159
MapSet.equal?(set_of_sha256(payload["zip_metadata"]), set_of_sha256(zip_metadata))
155160
end
156161

162+
def is_same_resource?(nil, _), do: false
163+
157164
def set_of_sha256(items), do: MapSet.new(items |> Enum.map(fn m -> Map.get(m, "sha256") || Map.get(m, :sha256) end))
158165

159166
defp store_resource_history!(%Resource{datagouv_id: datagouv_id}, payload) do
160167
Logger.debug("Saving ResourceHistory for #{datagouv_id}")
161168

162-
%DB.ResourceHistory{datagouv_id: datagouv_id, payload: payload}
169+
%DB.ResourceHistory{datagouv_id: datagouv_id, payload: payload, last_up_to_date_at: DateTime.utc_now()}
163170
|> DB.Repo.insert!()
164171
end
165172

173+
defp touch_resource_history!(%DB.ResourceHistory{id: id, datagouv_id: datagouv_id} = history) do
174+
Logger.debug("Touching unchanged ResourceHistory #{id} for resource datagouv_id #{datagouv_id}")
175+
176+
history |> Ecto.Changeset.change(%{last_up_to_date_at: DateTime.utc_now()}) |> DB.Repo.update!()
177+
end
178+
166179
defp download_path(%Resource{datagouv_id: datagouv_id}) do
167180
System.tmp_dir!() |> Path.join("resource_#{datagouv_id}_download")
168181
end

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

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ defmodule TransportWeb.DatasetController do
4343

4444
conn
4545
|> assign(:dataset, dataset)
46+
|> assign(:resources_related_files, DB.Dataset.get_resources_related_files(dataset))
4647
|> assign(:territory, territory)
4748
|> assign(:discussions, Discussions.get(dataset.datagouv_id))
4849
|> assign(:site, Application.get_env(:oauth2, Authentication)[:site])

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

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ defmodule TransportWeb.ResourceController do
4040
end
4141

4242
conn
43+
|> assign(:related_files, Resource.get_related_files(resource))
4344
|> assign(:resource, resource)
4445
|> assign(:other_resources, Resource.other_resources(resource))
4546
|> assign(:issues, Scrivener.paginate(issues, config))

apps/transport/lib/transport_web/templates/dataset/_resource.html.eex

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="panel resource <%= valid_panel_class(@resource) %>" title="<%= resource_tooltip_content(@resource) %>">
2-
<% has_associated_geojson = ResourceView.has_associated_geojson(@dataset, @resource) %>
2+
<% has_associated_geojson = ResourceView.has_associated_geojson(@resources_related_files, @resource.id) %>
33
<% has_associated_netex = ResourceView.has_associated_netex(@dataset, @resource) %>
44
<h4>
55
<%= @resource.title %>

apps/transport/lib/transport_web/templates/dataset/_resources_container.html.eex

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<% end %>
1010
<div>
1111
<div class="ressources-list">
12-
<%= render_many order_resources_by_validity(@resources), TransportWeb.DatasetView, "_resource.html", as: :resource, conn: @conn, dataset: assigns[:dataset] %>
12+
<%= render_many order_resources_by_validity(@resources), TransportWeb.DatasetView, "_resource.html", as: :resource, conn: @conn, resources_related_files: assigns[:resources_related_files], dataset: assigns[:dataset] %>
1313
</div>
1414

1515
<%= if assigns[:show_history_section_link] do %>

apps/transport/lib/transport_web/templates/dataset/details.html.eex

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<% gtfs_official_resources= gtfs_official_resources(@dataset) %>
6565
<% show_history_section_link= @history_resources !== [] and gtfs_official_resources !== [] %>
6666

67-
<%= render TransportWeb.DatasetView, "_resources_container.html", conn: @conn, resources: gtfs_official_resources, dataset: @dataset, title: dgettext("page-dataset-details", "GTFS resources"), show_history_section_link: show_history_section_link %>
67+
<%= render TransportWeb.DatasetView, "_resources_container.html", conn: @conn, resources: gtfs_official_resources, resources_related_files: @resources_related_files, dataset: @dataset, title: dgettext("page-dataset-details", "GTFS resources"), show_history_section_link: show_history_section_link %>
6868
<%= render TransportWeb.DatasetView, "_resources_container.html", conn: @conn, resources: gtfs_rt_official_resources(@dataset), title: dgettext("page-dataset-details", "GTFS real time resources") %>
6969
<%= render TransportWeb.DatasetView, "_resources_container.html", conn: @conn, resources: gbfs_official_resources(@dataset), title: dgettext("page-dataset-details", "GBFS resources") %>
7070
<%= render TransportWeb.DatasetView, "_resources_container.html", conn: @conn, resources: netex_official_resources(@dataset), title: dgettext("page-dataset-details", "NeTEx resources") %>

apps/transport/lib/transport_web/templates/resource/details.html.eex

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<section>
2-
<% associated_geojson = get_associated_geojson(@resource) %>
2+
<% associated_geojson = get_associated_geojson(@related_files) %>
33
<% associated_netex = get_associated_netex(@resource) %>
44
<div class="grey-background">
55
<div class="container">
@@ -26,6 +26,9 @@
2626
</ul>
2727
* <%= dgettext("validations", "GeoJSON format contains only the spatial information of the resource (stops coordinates, eventually lines shapes)
2828
It corresponds to the data you can see on the map below.")%>
29+
<%= unless is_nil(associated_geojson.resource_history_last_up_to_date_at) do %>
30+
<%= dgettext("validations", "This GeoJSON was up-to-date with the GTFS resource %{hours} ago.", hours: hours_ago(associated_geojson.resource_history_last_up_to_date_at))%>
31+
<% end %>
2932
</li>
3033
</div>
3134
<% end %>
@@ -63,13 +66,18 @@
6366
'resource-geojson',
6467
'resource-geojson-info',
6568
"<%= associated_geojson.url %>",
66-
"<%= associated_geojson.filesize %>",
69+
"<%= associated_geojson.filesize || 0 %>",
6770
"<%= dgettext("validations", "Stops visualization is quite big") %>",
6871
"<%= dgettext("validations", "Show anyway") %>"
69-
)
70-
})
71-
</script>
72-
</div>
72+
)
73+
})
74+
</script>
75+
</div>
76+
<%= unless is_nil(associated_geojson.resource_history_last_up_to_date_at) do %>
77+
<div class="is-centered no-margin">
78+
<%= dgettext("validations", "Visualization up-to-date %{hours} ago.", hours: hours_ago(associated_geojson.resource_history_last_up_to_date_at))%>
79+
</div>
80+
<% end %>
7381
<% end %>
7482
<h2 class="mt-48"><%= dgettext("validations", "Validation report")%></h2>
7583
<div class="panel" id="issues">

apps/transport/lib/transport_web/views/resource_view.ex

+36-5
Original file line numberDiff line numberDiff line change
@@ -101,25 +101,56 @@ defmodule TransportWeb.ResourceView do
101101

102102
def get_associated_resource(_resource, _format), do: nil
103103

104-
def has_associated_geojson(dataset, resource) do
105-
case get_associated_resource(dataset, resource, "geojson") do
104+
def has_associated_geojson(%{} = resources_related_files, resource_id) do
105+
resources_related_files
106+
|> Map.get(resource_id)
107+
|> get_associated_geojson()
108+
|> case do
106109
nil -> false
107110
_ -> true
108111
end
109112
end
110113

114+
def has_associated_geojson(_, _), do: false
115+
111116
def has_associated_netex(dataset, resource) do
112117
case get_associated_resource(dataset, resource, "NeTEx") do
113118
nil -> false
114119
_ -> true
115120
end
116121
end
117122

118-
def get_associated_geojson(resource) do
119-
get_associated_resource(resource, "geojson")
120-
end
123+
def get_associated_geojson(%{geojson: geojson_url}), do: geojson_url
124+
def get_associated_geojson(_), do: nil
121125

122126
def get_associated_netex(resource) do
123127
get_associated_resource(resource, "NeTEx")
124128
end
129+
130+
def hours_ago(utcdatetime) do
131+
DateTime.utc_now() |> DateTime.diff(utcdatetime) |> seconds_to_hours_minutes()
132+
end
133+
134+
@doc """
135+
Converts seconds to a string showing hours and minutes.
136+
Also work for negative input, even if not intended to use it that way.
137+
138+
iex> seconds_to_hours_minutes(3661)
139+
"1 h 1 min"
140+
iex> seconds_to_hours_minutes(60)
141+
"1 min"
142+
iex> seconds_to_hours_minutes(30)
143+
"0 min"
144+
iex> seconds_to_hours_minutes(-3661)
145+
"-1 h 1 min"
146+
"""
147+
@spec seconds_to_hours_minutes(integer()) :: binary()
148+
def seconds_to_hours_minutes(seconds) do
149+
hours = div(seconds, 3600)
150+
151+
case hours do
152+
0 -> "#{div(seconds, 60)} min"
153+
hours -> "#{hours} h #{seconds |> rem(3600) |> div(60) |> abs()} min"
154+
end
155+
end
125156
end

apps/transport/priv/gettext/en/LC_MESSAGES/validations.po

+8
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,11 @@ msgid "error"
121121
msgid_plural "errors"
122122
msgstr[0] ""
123123
msgstr[1] ""
124+
125+
#, elixir-format
126+
msgid "This GeoJSON was up-to-date with the GTFS resource %{hours} ago."
127+
msgstr ""
128+
129+
#, elixir-format
130+
msgid "Visualization up-to-date %{hours} ago."
131+
msgstr ""

apps/transport/priv/gettext/fr/LC_MESSAGES/validations.po

+8
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,11 @@ msgid "error"
121121
msgid_plural "errors"
122122
msgstr[0] "erreur"
123123
msgstr[1] "erreurs"
124+
125+
#, elixir-format
126+
msgid "This GeoJSON was up-to-date with the GTFS resource %{hours} ago."
127+
msgstr "Ce GeoJSON était bien à jour avec la ressource GTFS il y a %{hours}."
128+
129+
#, elixir-format
130+
msgid "Visualization up-to-date %{hours} ago."
131+
msgstr "Visualisation à jour il y a %{hours}."

apps/transport/priv/gettext/validations.pot

+8
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ msgid "error"
120120
msgid_plural "errors"
121121
msgstr[0] ""
122122
msgstr[1] ""
123+
124+
#, elixir-format
125+
msgid "This GeoJSON was up-to-date with the GTFS resource %{hours} ago."
126+
msgstr ""
127+
128+
#, elixir-format
129+
msgid "Visualization up-to-date %{hours} ago."
130+
msgstr ""

0 commit comments

Comments
 (0)