From 97cdd715f0f8f0c90d1f7abf862ad1459d033a79 Mon Sep 17 00:00:00 2001 From: Antoine Augusti Date: Thu, 30 Jan 2025 16:22:10 +0100 Subject: [PATCH] WIP --- .../client/stylesheets/reuser_space.scss | 4 ++ .../controllers/reuser_space_controller.ex | 15 +++++- .../live/backoffice/custom_tags_live.ex | 4 ++ .../plugs/custom_secure_browser_headers.ex | 26 +++++----- apps/transport/lib/transport_web/router.ex | 1 + .../reuser_space/datasets_edit.html.heex | 48 +++++++++++++++++-- .../transport_web/views/reuser_space_view.ex | 25 ++++++++++ .../gettext/en/LC_MESSAGES/reuser-space.po | 4 ++ .../gettext/fr/LC_MESSAGES/reuser-space.po | 4 ++ apps/transport/priv/gettext/reuser-space.pot | 4 ++ .../reuser_space_controller_test.exs | 23 +++++++++ .../views/reuser_space_view_test.exs | 30 ++++++++++++ config/config.exs | 1 + config/data_sharing_pilot.exs | 14 ++++++ 14 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 apps/transport/test/transport_web/views/reuser_space_view_test.exs create mode 100644 config/data_sharing_pilot.exs diff --git a/apps/transport/client/stylesheets/reuser_space.scss b/apps/transport/client/stylesheets/reuser_space.scss index 5b6b88e07c..0904afb9d6 100644 --- a/apps/transport/client/stylesheets/reuser_space.scss +++ b/apps/transport/client/stylesheets/reuser_space.scss @@ -54,3 +54,7 @@ form.search-followed-datasets { .align-right { text-align: right; } + +form.full-width { + max-width: 100%; +} diff --git a/apps/transport/lib/transport_web/controllers/reuser_space_controller.ex b/apps/transport/lib/transport_web/controllers/reuser_space_controller.ex index bcd8d07747..98bc6cb544 100644 --- a/apps/transport/lib/transport_web/controllers/reuser_space_controller.ex +++ b/apps/transport/lib/transport_web/controllers/reuser_space_controller.ex @@ -14,7 +14,20 @@ defmodule TransportWeb.ReuserSpaceController do |> render("index.html") end - def datasets_edit(%Plug.Conn{} = conn, _), do: render(conn, "datasets_edit.html") + def datasets_edit( + %Plug.Conn{assigns: %{dataset: %DB.Dataset{} = dataset, contact: %DB.Contact{} = contact}} = conn, + _ + ) do + conn + |> assign(:contact, DB.Repo.preload(contact, :organizations)) + |> assign(:dataset, DB.Repo.preload(dataset, :resources)) + |> render("datasets_edit.html") + end + + def add_improved_data(%Plug.Conn{} = conn, _) do + IO.inspect(conn) + conn |> text("Ok") + end def unfavorite(%Plug.Conn{assigns: %{dataset: %DB.Dataset{} = dataset, contact: %DB.Contact{} = contact}} = conn, _) do DB.DatasetFollower.unfollow!(contact, dataset) diff --git a/apps/transport/lib/transport_web/live/backoffice/custom_tags_live.ex b/apps/transport/lib/transport_web/live/backoffice/custom_tags_live.ex index bdb1f2a2b3..e677d2ad4a 100644 --- a/apps/transport/lib/transport_web/live/backoffice/custom_tags_live.ex +++ b/apps/transport/lib/transport_web/live/backoffice/custom_tags_live.ex @@ -76,6 +76,10 @@ defmodule TransportWeb.CustomTagsLive do %{ name: "experimental", doc: "Ajoute sur la page du JDD une bannière indiquant que le jeu est expérimental" + }, + %{ + name: Application.fetch_env!(:transport, :data_sharing_pilot_dataset_custom_tag), + doc: "Indique que ce jeu de données est éligible à l'expérimentation du repartage de données améliorées" } ] end diff --git a/apps/transport/lib/transport_web/plugs/custom_secure_browser_headers.ex b/apps/transport/lib/transport_web/plugs/custom_secure_browser_headers.ex index 31c71971a3..1c1786572d 100644 --- a/apps/transport/lib/transport_web/plugs/custom_secure_browser_headers.ex +++ b/apps/transport/lib/transport_web/plugs/custom_secure_browser_headers.ex @@ -9,7 +9,7 @@ defmodule TransportWeb.Plugs.CustomSecureBrowserHeaders do def call(conn, _opts) do nonce = generate_nonce() - csp_headers = csp_headers(Application.fetch_env!(:transport, :app_env), nonce) + csp_headers = csp_headers(Mix.env(), Application.fetch_env!(:transport, :app_env), nonce) headers = Map.merge(csp_headers, %{"x-frame-options" => "DENY"}) conn @@ -23,16 +23,16 @@ defmodule TransportWeb.Plugs.CustomSecureBrowserHeaders do Returns content-security-policy headers for an app environment. iex> nonce = "foo" - iex> match?(%{"content-security-policy" => _csp_content}, csp_headers(:production, nonce)) + iex> match?(%{"content-security-policy" => _csp_content}, csp_headers(:prod, :production, nonce)) true - iex> match?(%{"content-security-policy" => _csp_content}, csp_headers(:staging, nonce)) + iex> match?(%{"content-security-policy" => _csp_content}, csp_headers(:prod, :staging, nonce)) true - iex> csp_headers(:staging, nonce) != csp_headers(:production, nonce) + iex> csp_headers(:prod, :staging, nonce) != csp_headers(:prod, :production, nonce) true - iex> String.contains?("report-uri", csp_headers(:dev, nonce) |> Map.fetch!("content-security-policy")) + iex> String.contains?("report-uri", csp_headers(:dev, :dev, nonce) |> Map.fetch!("content-security-policy")) false """ - def csp_headers(app_env, nonce) do + def csp_headers(mix_env, app_env, nonce) do # https://github.com/vega/vega-embed/issues/1214#issuecomment-1670812445 vega_hash_values = "'sha256-9uoGUaZm3j6W7+Fh2wfvjI8P7zXcclRw5tVUu3qKZa0=' 'sha256-MmUum7+PiN7Rz79EUMm0OmUFWjCx6NZ97rdjoIbTnAg='" @@ -51,7 +51,7 @@ defmodule TransportWeb.Plugs.CustomSecureBrowserHeaders do "report-uri" => "" } |> Enum.map(fn {directive, value} -> - extra = " #{additional_content(directive, app_env)}" |> String.trim() + extra = " #{additional_content(directive, mix_env, app_env) |> String.trim()}" {directive, value <> extra} end) |> Enum.reject(fn {_, v} -> v == "" end) @@ -60,15 +60,19 @@ defmodule TransportWeb.Plugs.CustomSecureBrowserHeaders do %{"content-security-policy" => policy} end - defp additional_content("img-src", :staging) do - "https://demo-static.data.gouv.fr https://demo.data.gouv.fr" + defp additional_content("img-src", mix_env, app_env) do + if mix_env == :dev or app_env == :staging do + "https://demo-static.data.gouv.fr https://demo.data.gouv.fr" + else + "" + end end - defp additional_content("report-uri", app_env) when app_env in [:production, :staging] do + defp additional_content("report-uri", _mix_env, app_env) when app_env in [:production, :staging] do Application.fetch_env!(:sentry, :csp_url) end - defp additional_content(_directive, _app_env) do + defp additional_content(_directive, _mix_env, _app_env) do "" end end diff --git a/apps/transport/lib/transport_web/router.ex b/apps/transport/lib/transport_web/router.ex index cb9f5dfeb8..90dd249ca6 100644 --- a/apps/transport/lib/transport_web/router.ex +++ b/apps/transport/lib/transport_web/router.ex @@ -121,6 +121,7 @@ defmodule TransportWeb.Router do pipe_through([:reuser_space]) get("/", ReuserSpaceController, :espace_reutilisateur) get("/datasets/:dataset_id", ReuserSpaceController, :datasets_edit) + post("/datasets/:dataset_id/add_improved_data", ReuserSpaceController, :add_improved_data) post("/datasets/:dataset_id/unfavorite", ReuserSpaceController, :unfavorite) live_session :reuser_space, session: %{"role" => :reuser}, root_layout: {TransportWeb.LayoutView, :app} do diff --git a/apps/transport/lib/transport_web/templates/reuser_space/datasets_edit.html.heex b/apps/transport/lib/transport_web/templates/reuser_space/datasets_edit.html.heex index 2dab85bf9f..05efd4c64c 100644 --- a/apps/transport/lib/transport_web/templates/reuser_space/datasets_edit.html.heex +++ b/apps/transport/lib/transport_web/templates/reuser_space/datasets_edit.html.heex @@ -14,11 +14,51 @@

<%= dgettext("reuser-space", "Manage notifications") %>

<%= live_render(@conn, TransportWeb.Live.DatasetNotificationsLive, session: %{"dataset_id" => @dataset.id}) %> -
+

<%= dgettext("reuser-space", "Improved data sharing") %>

-

- <%= dgettext("reuser-space", "This feature is coming soon!") %> -

+ <%= if data_sharing_pilot?(@dataset, @contact) do %> + <% [organization] = data_sharing_eligible_org(@contact) %> +
+ +
+ <%= form_for @conn, reuser_space_path(@conn, :add_improved_data, @dataset.id), [class: "full-width"], fn f -> %> +
+ <%= for resource <- @dataset.resources |> Enum.filter(&DB.Resource.gtfs?/1) do %> +
+

+ <%= radio_button(f, :resource_id, resource.id, id: "resource-#{resource.id}", required: true) %> + <%= label f, resource.id, class: "label-inline", for: "resource-#{resource.id}" do %> + <%= resource.title %> + <% end %> +

+ +
+
+
+ <%= resource.format %> +
+
+
+
+ <% end %> +
+

+ <%= dgettext("reuser-space", "Only GTFS files are eligible for now.") %> +

+
+ <%= label(f, :url, dgettext("reuser-space", "Your improved data URL")) %> + <%= text_input(f, :url, type: "url", required: true) %> +
+ <%= submit(dgettext("reuser-space", "Share improved data"), class: "button") %> + <% end %> +

+ <%= dgettext("reuser-space", "You will be able to share back your data soon!") %> +

+ <% else %> +

+ <%= dgettext("reuser-space", "This feature is coming soon!") %> +

+ <% end %>

<%= dgettext("reuser-space", "Discussions") %>

diff --git a/apps/transport/lib/transport_web/views/reuser_space_view.ex b/apps/transport/lib/transport_web/views/reuser_space_view.ex index d7063fcc76..628b34279b 100644 --- a/apps/transport/lib/transport_web/views/reuser_space_view.ex +++ b/apps/transport/lib/transport_web/views/reuser_space_view.ex @@ -1,4 +1,29 @@ defmodule TransportWeb.ReuserSpaceView do use TransportWeb, :view import TransportWeb.BreadCrumbs, only: [breadcrumbs: 1] + + @doc """ + Is the following dataset eligible for the data sharing pilot for this contact, member + of various organizations? + """ + @spec data_sharing_pilot?(DB.Dataset.t(), DB.Contact.t()) :: boolean() + def data_sharing_pilot?(%DB.Dataset{} = dataset, %DB.Contact{} = contact) do + eligible_dataset_type = dataset.type == "public-transit" + has_dataset_tag = DB.Dataset.has_custom_tag?(dataset, config_value(:dataset_custom_tag)) + member_eligible_org = data_sharing_eligible_org(contact) |> Enum.count() == 1 + + Enum.all?([eligible_dataset_type, has_dataset_tag, member_eligible_org]) + end + + def data_sharing_eligible_org(%DB.Contact{organizations: organizations}) do + data_sharing_eligible_org(organizations) + end + + def data_sharing_eligible_org(organizations) when is_list(organizations) do + Enum.filter(organizations, &(&1.id in config_value(:eligible_datagouv_organization_ids))) + end + + defp config_value(key) do + Application.fetch_env!(:transport, :"data_sharing_pilot_#{key}") + end end diff --git a/apps/transport/priv/gettext/en/LC_MESSAGES/reuser-space.po b/apps/transport/priv/gettext/en/LC_MESSAGES/reuser-space.po index c38d3f235c..3393c9c3ac 100644 --- a/apps/transport/priv/gettext/en/LC_MESSAGES/reuser-space.po +++ b/apps/transport/priv/gettext/en/LC_MESSAGES/reuser-space.po @@ -135,6 +135,10 @@ msgstr "" msgid "This feature is coming soon!" msgstr "" +#, elixir-autogen, elixir-format +msgid "You will be able to share back your data soon!" +msgstr "" + #, elixir-autogen, elixir-format msgid "Warning, you are going to remove \"%{dataset_title}\" from your favorites. You will lose any settings or actions you previously performed on this dataset." msgstr "" diff --git a/apps/transport/priv/gettext/fr/LC_MESSAGES/reuser-space.po b/apps/transport/priv/gettext/fr/LC_MESSAGES/reuser-space.po index 78155322e4..1368e799a0 100644 --- a/apps/transport/priv/gettext/fr/LC_MESSAGES/reuser-space.po +++ b/apps/transport/priv/gettext/fr/LC_MESSAGES/reuser-space.po @@ -135,6 +135,10 @@ msgstr "Gérer" msgid "This feature is coming soon!" msgstr "Cette fonctionnalité arrive bientôt !" +#, elixir-autogen, elixir-format +msgid "You will be able to share back your data soon!" +msgstr "Vous pourrez repartager vos données bientôt !" + #, elixir-autogen, elixir-format msgid "Warning, you are going to remove \"%{dataset_title}\" from your favorites. You will lose any settings or actions you previously performed on this dataset." msgstr "Attention, vous allez supprimer le jeu de données \"%{dataset_title}\" de vos favoris. Vous allez perdre tous les paramétrages ou actions que vous avez réalisés précédemment sur ce jeu de données." diff --git a/apps/transport/priv/gettext/reuser-space.pot b/apps/transport/priv/gettext/reuser-space.pot index bd7c84242c..434546fcf2 100644 --- a/apps/transport/priv/gettext/reuser-space.pot +++ b/apps/transport/priv/gettext/reuser-space.pot @@ -135,6 +135,10 @@ msgstr "" msgid "This feature is coming soon!" msgstr "" +#, elixir-autogen, elixir-format +msgid "You will be able to share back your data soon!" +msgstr "" + #, elixir-autogen, elixir-format msgid "Warning, you are going to remove \"%{dataset_title}\" from your favorites. You will lose any settings or actions you previously performed on this dataset." msgstr "" diff --git a/apps/transport/test/transport_web/controllers/reuser_space_controller_test.exs b/apps/transport/test/transport_web/controllers/reuser_space_controller_test.exs index f6ab217596..e40ec3b55b 100644 --- a/apps/transport/test/transport_web/controllers/reuser_space_controller_test.exs +++ b/apps/transport/test/transport_web/controllers/reuser_space_controller_test.exs @@ -63,6 +63,29 @@ defmodule TransportWeb.ReuserSpaceControllerTest do |> Floki.find(".reuser-space-section h2") |> Floki.text() == dataset.custom_title end + + test "logged in, dataset is eligible for the data sharing pilot", %{conn: conn} do + # Google Maps org + organization = insert(:organization, id: "63fdfe4f4cd1c437ac478323") + dataset = insert(:dataset, custom_tags: ["repartage_donnees"], type: "public-transit") + + contact = + insert_contact(%{ + datagouv_user_id: Ecto.UUID.generate(), + organizations: [organization |> Map.from_struct()] + }) + + insert(:dataset_follower, contact_id: contact.id, dataset_id: dataset.id, source: :follow_button) + + assert conn + |> Plug.Test.init_test_session(%{current_user: %{"id" => contact.datagouv_user_id}}) + |> get(reuser_space_path(conn, :datasets_edit, dataset.id)) + |> html_response(200) + |> Floki.parse_document!() + |> Floki.find("#data-sharing p.notification") + |> Floki.text() + |> String.trim() == "Vous pourrez repartager vos données bientôt !" + end end describe "unfavorite" do diff --git a/apps/transport/test/transport_web/views/reuser_space_view_test.exs b/apps/transport/test/transport_web/views/reuser_space_view_test.exs new file mode 100644 index 0000000000..71dfcc69f1 --- /dev/null +++ b/apps/transport/test/transport_web/views/reuser_space_view_test.exs @@ -0,0 +1,30 @@ +defmodule TransportWeb.ReuserSpaceViewTest do + use ExUnit.Case, async: true + import TransportWeb.ReuserSpaceView + + @google_maps_org_id "63fdfe4f4cd1c437ac478323" + + setup do + Ecto.Adapters.SQL.Sandbox.checkout(DB.Repo) + end + + describe "data_sharing_pilot?" do + test "contact is not a member of an eligible organization" do + dataset = %DB.Dataset{type: "public-transit", custom_tags: ["repartage_donnees"]} + contact = %DB.Contact{organizations: []} + refute data_sharing_pilot?(dataset, contact) + end + + test "dataset does not have the required tag" do + dataset = %DB.Dataset{type: "public-transit", custom_tags: []} + contact = %DB.Contact{organizations: [%DB.Organization{id: @google_maps_org_id}]} + refute data_sharing_pilot?(dataset, contact) + end + + test "dataset is eligible for contact" do + dataset = %DB.Dataset{type: "public-transit", custom_tags: ["repartage_donnees"]} + contact = %DB.Contact{organizations: [%DB.Organization{id: @google_maps_org_id}]} + assert data_sharing_pilot?(dataset, contact) + end + end +end diff --git a/config/config.exs b/config/config.exs index fc8df2f239..7ed3c498f8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -246,4 +246,5 @@ import_config "gtfs_validator.exs" import_config "gbfs_validator.exs" import_config "mail.exs" import_config "mailchimp.exs" +import_config "data_sharing_pilot.exs" import_config "#{config_env()}.exs" diff --git a/config/data_sharing_pilot.exs b/config/data_sharing_pilot.exs new file mode 100644 index 0000000000..659b2faabd --- /dev/null +++ b/config/data_sharing_pilot.exs @@ -0,0 +1,14 @@ +import Config + +config :transport, + data_sharing_pilot_dataset_custom_tag: "repartage_donnees", + data_sharing_pilot_eligible_datagouv_organization_ids: [ + # transport.data.gouv.fr + "5abca8d588ee386ee6ece479", + # Google Maps + "63fdfe4f4cd1c437ac478323", + # Transit + "5c9a6477634f4133c7a5fc01", + # Citymapper / Via + "5f7cade93fb405c7d8f6d554" + ]