From 90e909742ac289aacb449493b98e22a181ad41a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaut=20Barr=C3=A8re?= Date: Mon, 16 Dec 2024 16:32:57 +0100 Subject: [PATCH 01/12] "Confinement" de la librairie Timex (#4385) * Extract iso-extended parsing to a central place * Add TODO * Add wrapper for "{YYYY}{0M}{0D}" * Add TODO * Move test calls * Wrap now() and shift() * Wrap Timex conversion calls * Add doctest wrapper * Add note * Fix credo warnings * Add doc * Update time_wrapper.ex * Forbid direct use of Timex --- .credo.exs | 7 ++- apps/shared/lib/date_time_display.ex | 13 ++--- apps/shared/lib/time_wrapper.ex | 48 +++++++++++++++++++ apps/shared/test/time_wrapper_test.exs | 4 ++ apps/transport/lib/jobs/gtfs_to_db.ex | 6 +-- .../transport_web/live/discussions_live.ex | 2 +- .../live_views/discussions_live_test.exs | 2 +- .../routing/headers_and_cookies_test.exs | 4 +- 8 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 apps/shared/lib/time_wrapper.ex create mode 100644 apps/shared/test/time_wrapper_test.exs diff --git a/.credo.exs b/.credo.exs index 0edcce4878..354c44d44a 100644 --- a/.credo.exs +++ b/.credo.exs @@ -129,10 +129,15 @@ # Deprecated checks (these will be deleted after a grace period) # - {Credo.Check.Readability.Specs, false} + {Credo.Check.Readability.Specs, false}, # Custom checks can be created using `mix credo.gen.check`. # + + {Credo.Check.Warning.ForbiddenModule, + [ + modules: [Timex] + ]} ] } ] diff --git a/apps/shared/lib/date_time_display.ex b/apps/shared/lib/date_time_display.ex index 2b39f18931..6da044c163 100644 --- a/apps/shared/lib/date_time_display.ex +++ b/apps/shared/lib/date_time_display.ex @@ -34,7 +34,7 @@ defmodule Shared.DateTimeDisplay do def format_date(nil, _), do: "" def format_date(date, locale, iso_extended: true) do - date |> Timex.parse!("{ISO:Extended}") |> format_date(locale) + date |> TimeWrapper.parse!("{ISO:Extended}") |> format_date(locale) end @doc """ @@ -100,7 +100,7 @@ defmodule Shared.DateTimeDisplay do def format_datetime_to_paris(datetime, locale, options) when is_binary(datetime) do datetime - |> Timex.parse!("{ISO:Extended}") + |> TimeWrapper.parse!("{ISO:Extended}") |> format_datetime_to_paris(locale, options) end @@ -157,7 +157,7 @@ defmodule Shared.DateTimeDisplay do def format_time_to_paris(datetime, locale, options) when is_binary(datetime) do datetime - |> Timex.parse!("{ISO:Extended}") + |> TimeWrapper.parse!("{ISO:Extended}") |> format_time_to_paris(locale, options) end @@ -225,14 +225,11 @@ defmodule Shared.DateTimeDisplay do @spec convert_to_paris_time(DateTime.t() | NaiveDateTime.t()) :: DateTime.t() def convert_to_paris_time(%DateTime{} = dt) do - case Timex.Timezone.convert(dt, "Europe/Paris") do - %Timex.AmbiguousDateTime{after: dt} -> dt - %DateTime{} = dt -> dt - end + TimeWrapper.convert_to_paris_time(dt) end def convert_to_paris_time(%NaiveDateTime{} = ndt) do - ndt |> Timex.Timezone.convert("UTC") |> convert_to_paris_time() + ndt |> TimeWrapper.convert("UTC") |> convert_to_paris_time() end defp get_localized_datetime_format("en" = locale, options) do diff --git a/apps/shared/lib/time_wrapper.ex b/apps/shared/lib/time_wrapper.ex new file mode 100644 index 0000000000..c19d9b2385 --- /dev/null +++ b/apps/shared/lib/time_wrapper.ex @@ -0,0 +1,48 @@ +defmodule TimeWrapper do + @moduledoc """ + This module concentrates all the calls to `Timex` in a single place. + + The idea behind this module is 1. to reduce our dependency on `Timex`, and + 2. to ideally gradually replace calls by built-in Elixir `DateTime` calls, since + `Timex` filled a void in the language that has been partially filled now. + """ + + # credo:disable-for-this-file Credo.Check.Warning.ForbiddenModule + + def parse!(date_as_string, "{ISO:Extended}" = param) do + Timex.parse!(date_as_string, param) + end + + def parse!(date_as_string, "{YYYY}{0M}{0D}" = param) do + Timex.parse!(date_as_string, param) + end + + # NOTE: try not to use this, we will remove it. This is rfc2822 ; + # Plug encodes it, but there is no built-in decoder. + def parse!(datetime_as_string, "{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} GMT" = param) do + Timex.parse!(datetime_as_string, param) + end + + def diff(first, second, :hours = param) do + Timex.diff(first, second, param) + end + + def now do + Timex.now() + end + + def shift(dt, months: months) do + Timex.shift(dt, months: months) + end + + def convert(dt, "UTC") do + Timex.Timezone.convert(dt, "UTC") + end + + def convert_to_paris_time(dt) do + case Timex.Timezone.convert(dt, "Europe/Paris") do + %Timex.AmbiguousDateTime{after: dt} -> dt + %DateTime{} = dt -> dt + end + end +end diff --git a/apps/shared/test/time_wrapper_test.exs b/apps/shared/test/time_wrapper_test.exs new file mode 100644 index 0000000000..9a6eff0309 --- /dev/null +++ b/apps/shared/test/time_wrapper_test.exs @@ -0,0 +1,4 @@ +defmodule TimeWrapperTest do + use ExUnit.Case, async: true + doctest TimeWrapper +end diff --git a/apps/transport/lib/jobs/gtfs_to_db.ex b/apps/transport/lib/jobs/gtfs_to_db.ex index 238e8a7207..2b64b38c9f 100644 --- a/apps/transport/lib/jobs/gtfs_to_db.ex +++ b/apps/transport/lib/jobs/gtfs_to_db.ex @@ -120,8 +120,8 @@ defmodule Transport.Jobs.GtfsToDB do friday: friday = r |> Map.fetch!("friday") |> String.to_integer(), saturday: saturday = r |> Map.fetch!("saturday") |> String.to_integer(), sunday: sunday = r |> Map.fetch!("sunday") |> String.to_integer(), - start_date: r |> Map.fetch!("start_date") |> Timex.parse!("{YYYY}{0M}{0D}") |> NaiveDateTime.to_date(), - end_date: r |> Map.fetch!("end_date") |> Timex.parse!("{YYYY}{0M}{0D}") |> NaiveDateTime.to_date() + start_date: r |> Map.fetch!("start_date") |> TimeWrapper.parse!("{YYYY}{0M}{0D}") |> NaiveDateTime.to_date(), + end_date: r |> Map.fetch!("end_date") |> TimeWrapper.parse!("{YYYY}{0M}{0D}") |> NaiveDateTime.to_date() } res @@ -214,7 +214,7 @@ defmodule Transport.Jobs.GtfsToDB do %{ data_import_id: data_import_id, service_id: r |> Map.fetch!("service_id"), - date: r |> Map.fetch!("date") |> Timex.parse!("{YYYY}{0M}{0D}") |> NaiveDateTime.to_date(), + date: r |> Map.fetch!("date") |> TimeWrapper.parse!("{YYYY}{0M}{0D}") |> NaiveDateTime.to_date(), exception_type: r |> Map.fetch!("exception_type") |> String.to_integer() } end) diff --git a/apps/transport/lib/transport_web/live/discussions_live.ex b/apps/transport/lib/transport_web/live/discussions_live.ex index 476ecb28fe..577ef4569f 100644 --- a/apps/transport/lib/transport_web/live/discussions_live.ex +++ b/apps/transport/lib/transport_web/live/discussions_live.ex @@ -115,7 +115,7 @@ defmodule TransportWeb.DiscussionsLive do end) |> Enum.max(DateTime) - two_months_ago = DateTime.utc_now() |> Timex.shift(months: -2) + two_months_ago = DateTime.utc_now() |> TimeWrapper.shift(months: -2) DateTime.compare(two_months_ago, latest_comment_datetime) == :gt end diff --git a/apps/transport/test/transport_web/live_views/discussions_live_test.exs b/apps/transport/test/transport_web/live_views/discussions_live_test.exs index 5f5e7fc469..92e47f0a31 100644 --- a/apps/transport/test/transport_web/live_views/discussions_live_test.exs +++ b/apps/transport/test/transport_web/live_views/discussions_live_test.exs @@ -209,7 +209,7 @@ defmodule Transport.TransportWeb.DiscussionsLiveTest do end defp iso8601_string_x_months_ago(x) do - DateTime.utc_now() |> Timex.shift(months: -x) |> DateTime.to_iso8601() + DateTime.utc_now() |> TimeWrapper.shift(months: -x) |> DateTime.to_iso8601() end defp discussions do diff --git a/apps/transport/test/transport_web/routing/headers_and_cookies_test.exs b/apps/transport/test/transport_web/routing/headers_and_cookies_test.exs index 2b1d7bffe5..49d566d0a1 100644 --- a/apps/transport/test/transport_web/routing/headers_and_cookies_test.exs +++ b/apps/transport/test/transport_web/routing/headers_and_cookies_test.exs @@ -24,7 +24,7 @@ defmodule TransportWeb.HeadersAndCookiesTest do "expires" => datetime } = Plug.Conn.Cookies.decode(header) - datetime = Timex.parse!(datetime, "{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") - assert_in_delta Timex.diff(datetime, Timex.now(), :hours), 15 * 24, 1 + datetime = TimeWrapper.parse!(datetime, "{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") + assert_in_delta TimeWrapper.diff(datetime, TimeWrapper.now(), :hours), 15 * 24, 1 end end From 8f9d6d3ee13b02ec459e625c80f487a36f157ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaut=20Barr=C3=A8re?= Date: Tue, 17 Dec 2024 14:24:14 +0100 Subject: [PATCH 02/12] =?UTF-8?q?Correctif=20&=20am=C3=A9lioration=20outil?= =?UTF-8?q?=20de=20scan=20IRVE=20back-office=20(#4389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add repro for #4383 * Fix #4383 (broken clause for HTTP non-200) * Bubble up HTTP status * Fix exception, field is nil for resources in 404 * mix format * When non-200 status is seen, ignore actual validity * Fix credo warning --- apps/transport/lib/irve/extractor.ex | 7 ++-- .../live/backoffice/irve_dashboard_live.ex | 17 +++++++--- .../backoffice/irve_dashboard_live.html.heex | 4 +-- .../transport/irve/irve_extractor_test.exs | 32 +++++++++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/apps/transport/lib/irve/extractor.ex b/apps/transport/lib/irve/extractor.ex index e51738adec..f0625e33b2 100644 --- a/apps/transport/lib/irve/extractor.ex +++ b/apps/transport/lib/irve/extractor.ex @@ -111,6 +111,7 @@ defmodule Transport.IRVE.Extractor do |> Enum.map(fn x -> Map.take(x, [ :dataset_id, + :http_status, :dataset_title, :dataset_organisation_name, :dataset_organisation_url, @@ -133,7 +134,7 @@ defmodule Transport.IRVE.Extractor do Transport.IRVE.Fetcher.get!(row[:url], compressed: false, decode_body: false) row - |> Map.put(:status, status) + |> Map.put(:http_status, status) |> Map.put(:index, index) |> then(fn x -> process_resource_body(x, body) end) end @@ -142,7 +143,7 @@ defmodule Transport.IRVE.Extractor do For a given resource and corresponding body, enrich data with extra stuff like estimated number of charge points. """ - def process_resource_body(%{status: 200} = row, body) do + def process_resource_body(%{http_status: 200} = row, body) do body = body |> String.split("\n") first_line = body |> hd() line_count = (body |> length) - 1 @@ -157,7 +158,7 @@ defmodule Transport.IRVE.Extractor do |> Map.put(:line_count, line_count) end - def process_body(row), do: row + def process_resource_body(row, _body), do: row @doc """ Save the outcome in the database for reporting. diff --git a/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.ex b/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.ex index 25623eae92..4088708b62 100644 --- a/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.ex +++ b/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.ex @@ -3,7 +3,7 @@ defmodule TransportWeb.Backoffice.IRVEDashboardLive do use Phoenix.HTML import TransportWeb.Backoffice.JobsLive, only: [ensure_admin_auth_or_redirect: 3] import TransportWeb.Router.Helpers - import Helpers, only: [format_number: 1] + import Helpers, only: [format_number_maybe_nil: 2] import Ecto.Query, only: [last: 2] @impl true @@ -25,7 +25,12 @@ defmodule TransportWeb.Backoffice.IRVEDashboardLive do filtering_expression == "" || String.contains?(resource["dataset_organisation_name"] |> String.downcase(), filtering_expression) || - String.contains?(format_validity(resource["valid"]) |> inspect |> String.downcase(), filtering_expression) + String.contains?( + format_validity(resource["valid"], resource["http_status"]) + |> inspect + |> String.downcase(), + filtering_expression + ) end def assign_data(socket) do @@ -119,7 +124,9 @@ defmodule TransportWeb.Backoffice.IRVEDashboardLive do |> Enum.sort_by(fn x -> x["line_count"] end, :desc) end - def format_validity(false), do: {:safe, "KO"} - def format_validity(true), do: "OK" - def format_validity(nil), do: "Non testé" + def format_validity(false, 200), do: {:safe, "KO"} + def format_validity(true, 200), do: "OK" + def format_validity(nil, 200), do: "Non testé" + # mostly there to handle 404/500. Ignore validity and assume the resource is not reachable at all + def format_validity(_validity, http_status), do: {:safe, "KO (#{http_status})"} end diff --git a/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.html.heex b/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.html.heex index eef1805a15..717a4ed912 100644 --- a/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.html.heex +++ b/apps/transport/lib/transport_web/live/backoffice/irve_dashboard_live.html.heex @@ -43,9 +43,9 @@ — <%= resource["last_modified"] |> String.slice(0..15) %> - <%= format_validity(resource["valid"]) %> + <%= format_validity(resource["valid"], resource["http_status"]) %> - <%= format_number(resource["line_count"]) %> + <%= format_number_maybe_nil(resource["line_count"], nil_result: "???") %> <% end %> diff --git a/apps/transport/test/transport/irve/irve_extractor_test.exs b/apps/transport/test/transport/irve/irve_extractor_test.exs index bdebcf8600..0a1b27401c 100644 --- a/apps/transport/test/transport/irve/irve_extractor_test.exs +++ b/apps/transport/test/transport/irve/irve_extractor_test.exs @@ -117,6 +117,38 @@ defmodule Transport.IRVE.ExtractorTest do orig_resource |> Map.put(:index, 0) |> Map.put(:line_count, 3) + |> Map.put(:http_status, 200) + |> Map.delete(:url) + ] + end + + test "handles non-200 response" do + resources = [ + orig_resource = %{ + url: expected_url = "https://static.data.gouv.fr/resources/something/something.csv", + dataset_id: "the-dataset-id", + dataset_title: "the-dataset-title", + resource_id: "the-resource-id", + resource_title: "the-resource-title", + valid: true + } + ] + + Transport.Req.Mock + |> expect(:get!, fn _request, options -> + assert options[:url] == expected_url + + %Req.Response{ + status: 404, + body: "there is nothing here" + } + end) + + # parsed resources must be enriched with line count & index, and url removed + assert Transport.IRVE.Extractor.download_and_parse_all(resources) == [ + orig_resource + |> Map.put(:index, 0) + |> Map.put(:http_status, 404) |> Map.delete(:url) ] end From 78a21dbcce8f66ae783fea459bc61f6d6c3054a1 Mon Sep 17 00:00:00 2001 From: Antoine Augusti Date: Thu, 19 Dec 2024 08:50:00 +0100 Subject: [PATCH 03/12] =?UTF-8?q?Suppression=20feature=20flag=20espace=20r?= =?UTF-8?q?=C3=A9utilisateur=20(#4395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/transport/lib/transport_web/router.ex | 12 ------ apps/transport/lib/transport_web/session.ex | 17 -------- .../templates/dataset/_header_links.html.heex | 42 +++++++++---------- .../templates/dataset/details.html.heex | 8 ++-- .../templates/dataset/index.html.heex | 4 +- .../templates/layout/_header.html.heex | 8 ++-- .../templates/page/index.html.heex | 14 +++---- apps/transport/priv/gettext/alert.pot | 4 -- .../priv/gettext/en/LC_MESSAGES/alert.po | 4 -- .../priv/gettext/fr/LC_MESSAGES/alert.po | 4 -- .../reuser_space_controller_test.exs | 11 ----- .../test/transport_web/session_test.exs | 18 -------- config/runtime.exs | 1 - 13 files changed, 33 insertions(+), 114 deletions(-) diff --git a/apps/transport/lib/transport_web/router.ex b/apps/transport/lib/transport_web/router.ex index 39ec8b7c28..cb9f5dfeb8 100644 --- a/apps/transport/lib/transport_web/router.ex +++ b/apps/transport/lib/transport_web/router.ex @@ -56,7 +56,6 @@ defmodule TransportWeb.Router do pipeline :reuser_space do plug(:browser) plug(:authentication_required, destination_path: "/infos_reutilisateurs") - plug(:check_reuser_space_enabled) end scope "/", OpenApiSpex.Plug do @@ -387,17 +386,6 @@ defmodule TransportWeb.Router do end end - def check_reuser_space_enabled(%Plug.Conn{} = conn, _) do - if TransportWeb.Session.display_reuser_space?(conn) do - conn - else - conn - |> put_flash(:info, dgettext("alert", "This feature is currently not available.")) - |> redirect(to: "/") - |> halt() - end - end - # Check that a secret key is passed in the URL in the `export_key` query parameter defp check_export_secret_key(%Plug.Conn{params: params} = conn, _) do export_key_value = Map.get(params, "export_key", "") diff --git a/apps/transport/lib/transport_web/session.ex b/apps/transport/lib/transport_web/session.ex index 27aebfad97..e8f73874a2 100644 --- a/apps/transport/lib/transport_web/session.ex +++ b/apps/transport/lib/transport_web/session.ex @@ -58,23 +58,6 @@ defmodule TransportWeb.Session do DB.Dataset.base_query() |> where([dataset: d], d.organization_id in ^org_ids) |> DB.Repo.exists?() end - @doc """ - A temporary helper method to determine if we should display "reuser space features". - Convenient method to find various entrypoints in the codebase: - - links and buttons to the reuser space - - follow dataset hearts (search results, dataset pages) - - reuser space - - Enable it for everybody but keep a "kill switch" to disable it quickly - by setting an environment variable and rebooting the app. - - transport.data.gouv.fr admins get access no matter what. - """ - def display_reuser_space?(%Plug.Conn{} = conn) do - feature_disabled = Application.fetch_env!(:transport, :disable_reuser_space) - admin?(conn) or not feature_disabled - end - @spec set_session_attribute_attribute(Plug.Conn.t(), binary(), boolean()) :: Plug.Conn.t() defp set_session_attribute_attribute(%Plug.Conn{} = conn, key, value) do current_user = current_user(conn) diff --git a/apps/transport/lib/transport_web/templates/dataset/_header_links.html.heex b/apps/transport/lib/transport_web/templates/dataset/_header_links.html.heex index 0515fd571d..25a3964251 100644 --- a/apps/transport/lib/transport_web/templates/dataset/_header_links.html.heex +++ b/apps/transport/lib/transport_web/templates/dataset/_header_links.html.heex @@ -3,32 +3,30 @@ <%= link("Backoffice", to: backoffice_page_path(@conn, :edit, @dataset.id)) %> · <% end %> - <%= if TransportWeb.Session.display_reuser_space?(@conn) do %> - - <%= if @current_user do %> - <%= if @is_producer do %> - <%= link(dgettext("default", "Producer space"), - to: espace_producteur_path(@conn, :edit_dataset, @dataset.id, utm_campaign: "dataset_details"), + + <%= if @current_user do %> + <%= if @is_producer do %> + <%= link(dgettext("default", "Producer space"), + to: espace_producteur_path(@conn, :edit_dataset, @dataset.id, utm_campaign: "dataset_details"), + target: "_blank" + ) %> + <% else %> + <%= if @follows_dataset do %> + <%= link(dgettext("default", "Reuser space"), + to: reuser_space_path(@conn, :datasets_edit, @dataset.id, utm_campaign: "dataset_details"), target: "_blank" ) %> <% else %> - <%= if @follows_dataset do %> - <%= link(dgettext("default", "Reuser space"), - to: reuser_space_path(@conn, :datasets_edit, @dataset.id, utm_campaign: "dataset_details"), - target: "_blank" - ) %> - <% else %> - <%= link(dgettext("default", "Reuser space"), - to: reuser_space_path(@conn, :espace_reutilisateur, utm_campaign: "dataset_details"), - target: "_blank" - ) %> - <% end %> + <%= link(dgettext("default", "Reuser space"), + to: reuser_space_path(@conn, :espace_reutilisateur, utm_campaign: "dataset_details"), + target: "_blank" + ) %> <% end %> - <% else %> - <%= link(dgettext("default", "Reuser space"), - to: page_path(@conn, :infos_reutilisateurs, utm_campaign: "dataset_details"), - target: "_blank" - ) %> <% end %> + <% else %> + <%= link(dgettext("default", "Reuser space"), + to: page_path(@conn, :infos_reutilisateurs, utm_campaign: "dataset_details"), + target: "_blank" + ) %> <% end %> diff --git a/apps/transport/lib/transport_web/templates/dataset/details.html.heex b/apps/transport/lib/transport_web/templates/dataset/details.html.heex index 10de3427b1..accdd4cbe0 100644 --- a/apps/transport/lib/transport_web/templates/dataset/details.html.heex +++ b/apps/transport/lib/transport_web/templates/dataset/details.html.heex @@ -239,11 +239,9 @@
- <%= if TransportWeb.Session.display_reuser_space?(@conn) do %> - <%= live_render(@conn, TransportWeb.Live.FollowDatasetLive, - session: %{"current_user" => @current_user, "dataset_id" => @dataset.id} - ) %> - <% end %> + <%= live_render(@conn, TransportWeb.Live.FollowDatasetLive, + session: %{"current_user" => @current_user, "dataset_id" => @dataset.id} + ) %> diff --git a/apps/transport/lib/transport_web/templates/dataset/index.html.heex b/apps/transport/lib/transport_web/templates/dataset/index.html.heex index 4e301295fe..86a7d9e53e 100644 --- a/apps/transport/lib/transport_web/templates/dataset/index.html.heex +++ b/apps/transport/lib/transport_web/templates/dataset/index.html.heex @@ -196,9 +196,7 @@
- <%= if not is_nil(@current_user) and TransportWeb.Session.display_reuser_space?(@conn) do %> - - <% end %> + <%= unless is_nil(icon_type_path(dataset)) do %> <%= img_tag(icon_type_path(dataset), alt: dataset.type) %> <% end %> diff --git a/apps/transport/lib/transport_web/templates/layout/_header.html.heex b/apps/transport/lib/transport_web/templates/layout/_header.html.heex index 6e222369e7..3950024dfb 100644 --- a/apps/transport/lib/transport_web/templates/layout/_header.html.heex +++ b/apps/transport/lib/transport_web/templates/layout/_header.html.heex @@ -97,11 +97,9 @@ to: page_path(@conn, :espace_producteur, utm_campaign: "menu_dropdown") ) %> <% end %> - <%= if TransportWeb.Session.display_reuser_space?(@conn) do %> - <%= link(gettext("Reuser space"), - to: reuser_space_path(@conn, :espace_reutilisateur, utm_campaign: "menu_dropdown") - ) %> - <% end %> + <%= link(gettext("Reuser space"), + to: reuser_space_path(@conn, :espace_reutilisateur, utm_campaign: "menu_dropdown") + ) %> <%= dgettext("page-index", "Producer space") %> - <%= if TransportWeb.Session.display_reuser_space?(@conn) do %> - - <%= dgettext("page-index", "Reuser space") %> - - <% end %> + + <%= dgettext("page-index", "Reuser space") %> +