Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Espace réutilisateur : formulaire pour ajouter des données améliorées #4437

Merged
merged 3 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/transport/client/stylesheets/reuser_space.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ form.search-followed-datasets {
.align-right {
text-align: right;
}

form.full-width {
max-width: 100%;
}
30 changes: 30 additions & 0 deletions apps/transport/lib/db/reuser_improved_data.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule DB.ReuserImprovedData do
@moduledoc """
Represents improved static data shared by reusers.
"""
use Ecto.Schema
use TypedEctoSchema
import Ecto.Changeset

typed_schema "reuser_improved_data" do
belongs_to(:dataset, DB.Dataset)
belongs_to(:resource, DB.Resource)
belongs_to(:contact, DB.Contact)
belongs_to(:organization, DB.Organization, type: :string)
field(:download_url, :string)
timestamps(type: :utc_datetime_usec)
end

def changeset(%__MODULE__{} = struct, attrs \\ %{}) do
fields = [:dataset_id, :resource_id, :contact_id, :organization_id, :download_url]

struct
|> cast(attrs, fields)
|> validate_required(fields)
|> assoc_constraint(:dataset)
|> assoc_constraint(:resource)
|> assoc_constraint(:contact)
|> assoc_constraint(:organization)
|> unique_constraint([:dataset_id, :organization_id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je m'attendais à voir le resource_id membre de la clef d'unicité. Un réutilisateur ne fournit-il pas une version améliorée d'une ressource ?

end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule TransportWeb.ReuserSpaceController do
use TransportWeb, :controller
import Ecto.Query

plug(:find_dataset_or_redirect when action in [:datasets_edit, :unfavorite])
plug(:find_dataset_or_redirect when action in [:datasets_edit, :unfavorite, :add_improved_data])

def espace_reutilisateur(%Plug.Conn{assigns: %{current_user: %{"id" => datagouv_user_id}}} = conn, _) do
contact = DB.Repo.get_by!(DB.Contact, datagouv_user_id: datagouv_user_id)
Expand All @@ -14,7 +14,54 @@ 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
contact = DB.Repo.preload(contact, :organizations)
eligible_organizations = data_sharing_eligible_orgs(contact)

conn
|> assign(:contact, contact)
|> assign(:dataset, DB.Repo.preload(dataset, :resources))
|> assign(:eligible_to_data_sharing_pilot, data_sharing_pilot?(dataset, contact))
|> assign(:eligible_organizations, eligible_organizations)
|> assign(:existing_improved_data, existing_improved_data(dataset, eligible_organizations))
|> render("datasets_edit.html")
end

defp existing_improved_data(%DB.Dataset{id: dataset_id}, [%DB.Organization{id: organization_id}]) do
DB.ReuserImprovedData
|> where([r], r.dataset_id == ^dataset_id and r.organization_id == ^organization_id)
|> DB.Repo.one()
end
Comment on lines +33 to +37
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Une ressource améliorée est déposée au nom de l'organisation éligible au pilote, ce n'est pas scopé par le contact qui a fait l'action (même si le contact est bien enregistré)


defp existing_improved_data(%DB.Dataset{}, _orgs), do: nil

def add_improved_data(
%Plug.Conn{
assigns: %{dataset: %DB.Dataset{} = dataset, contact: %DB.Contact{} = contact},
params: %{
"resource_id" => resource_id,
"organization_id" => organization_id,
"download_url" => download_url
}
} = conn,
_
) do
DB.ReuserImprovedData.changeset(%DB.ReuserImprovedData{}, %{
dataset_id: dataset.id,
resource_id: resource_id,
contact_id: contact.id,
organization_id: organization_id,
download_url: download_url
})
|> DB.Repo.insert!()

conn
|> put_flash(:info, dgettext("reuser-space", "Your improved data has been saved."))
|> redirect(to: reuser_space_path(conn, :datasets_edit, dataset.id))
end

def unfavorite(%Plug.Conn{assigns: %{dataset: %DB.Dataset{} = dataset, contact: %DB.Contact{} = contact}} = conn, _) do
DB.DatasetFollower.unfollow!(contact, dataset)
Expand Down Expand Up @@ -59,4 +106,29 @@ defmodule TransportWeb.ReuserSpaceController do
|> halt()
end
end

@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_orgs(contact) |> Enum.count() == 1

Enum.all?([eligible_dataset_type, has_dataset_tag, member_eligible_org])
end

def data_sharing_eligible_orgs(%DB.Contact{organizations: organizations}) do
data_sharing_eligible_orgs(organizations)
end

def data_sharing_eligible_orgs(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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est hors scope mais je corrige la Content Security Policy. En local les images des organisations/utilisateurs n'étaient pas affichés car le domaine demo-static.data.gouv.fr n'était pas autorisé

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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='"
Expand All @@ -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)
Expand All @@ -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
1 change: 1 addition & 0 deletions apps/transport/lib/transport_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,61 @@
<h3><%= dgettext("reuser-space", "Manage notifications") %></h3>
<%= live_render(@conn, TransportWeb.Live.DatasetNotificationsLive, session: %{"dataset_id" => @dataset.id}) %>
</div>
<div class="panel">
<div id="data-sharing" class="panel">
<h3><%= dgettext("reuser-space", "Improved data sharing") %></h3>
<p class="notification">
<%= dgettext("reuser-space", "This feature is coming soon!") %>
</p>
<%= if @eligible_to_data_sharing_pilot do %>
<% [organization] = @eligible_organizations %>
<%= if is_nil(@existing_improved_data) do %>
<div class="align-right">
<img src={organization.logo_thumbnail} title={organization.name} />
</div>
<%= form_for @conn, reuser_space_path(@conn, :add_improved_data, @dataset.id), [class: "full-width"], fn f -> %>
<h4>
<%= dgettext("reuser-space", "Step 1: choose the initial resource from the producer") %>
</h4>
<div class="ressources-list">
<%= for resource <- @dataset.resources |> Enum.filter(&DB.Resource.gtfs?/1) do %>
<div class="panel resource">
<h4>
<%= 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 %>
</h4>

<div class="resource-panel-bottom">
<div class="resource-actions">
<div class="resource-format" title={dgettext("page-dataset-details", "resource format")}>
<span class="label"><%= resource.format %></span>
</div>
</div>
</div>
</div>
<% end %>
</div>
<p class="small">
<%= dgettext("reuser-space", "Only GTFS files are eligible for now.") %>
</p>
<div class="form__group">
<h4>
<%= dgettext("reuser-space", "Step 2: add the URL to download your improved data") %>
</h4>
<%= label(f, :download_url, dgettext("reuser-space", "Your improved data URL")) %>
<%= text_input(f, :download_url, type: "url", required: true) %>
</div>
<%= hidden_input(f, :organization_id, value: organization.id) %>
<%= submit(dgettext("reuser-space", "Share improved data"), class: "button") %>
<% end %>
<% else %>
<p class="notification">
<%= dgettext("reuser-space", "You already shared improved data for this dataset, thanks!") %>
</p>
<% end %>
<% else %>
<p class="notification">
<%= dgettext("reuser-space", "This feature is coming soon!") %>
</p>
<% end %>
</div>
<div class="panel">
<h3><%= dgettext("reuser-space", "Discussions") %></h3>
Expand Down
28 changes: 28 additions & 0 deletions apps/transport/priv/gettext/en/LC_MESSAGES/reuser-space.po
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,31 @@ 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 ""

#, elixir-autogen, elixir-format
msgid "Only GTFS files are eligible for now."
msgstr ""

#, elixir-autogen, elixir-format
msgid "Share improved data"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Your improved data URL"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Step 1: choose the initial resource from the producer"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Step 2: add the URL to download your improved data"
msgstr ""

#, elixir-autogen, elixir-format
msgid "You already shared improved data for this dataset, thanks!"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Your improved data has been saved."
msgstr ""
28 changes: 28 additions & 0 deletions apps/transport/priv/gettext/fr/LC_MESSAGES/reuser-space.po
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,31 @@ msgstr "Cette fonctionnalité arrive 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."

#, elixir-autogen, elixir-format
msgid "Only GTFS files are eligible for now."
msgstr "Seuls les fichiers GTFS sont éligibles pour le moment."

#, elixir-autogen, elixir-format
msgid "Share improved data"
msgstr "Repartager vos données améliorées"

#, elixir-autogen, elixir-format
msgid "Your improved data URL"
msgstr "Lien vers vos données améliorées"

#, elixir-autogen, elixir-format
msgid "Step 1: choose the initial resource from the producer"
msgstr "Étape 1 : sélectionnez la ressource initiale du producteur"

#, elixir-autogen, elixir-format
msgid "Step 2: add the URL to download your improved data"
msgstr "Étape 2 : renseignez l'URL de téléchargement de votre fichier amélioré"

#, elixir-autogen, elixir-format
msgid "You already shared improved data for this dataset, thanks!"
msgstr "Vous avez déjà partagé des données améliorées pour ce jeu de données, merci !"

#, elixir-autogen, elixir-format
msgid "Your improved data has been saved."
msgstr "Vos données améliorées ont bien été sauvegardées."
28 changes: 28 additions & 0 deletions apps/transport/priv/gettext/reuser-space.pot
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,31 @@ 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 ""

#, elixir-autogen, elixir-format
msgid "Only GTFS files are eligible for now."
msgstr ""

#, elixir-autogen, elixir-format
msgid "Share improved data"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Your improved data URL"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Step 1: choose the initial resource from the producer"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Step 2: add the URL to download your improved data"
msgstr ""

#, elixir-autogen, elixir-format
msgid "You already shared improved data for this dataset, thanks!"
msgstr ""

#, elixir-autogen, elixir-format
msgid "Your improved data has been saved."
msgstr ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule DB.Repo.Migrations.ReuserImprovedData do
use Ecto.Migration

def change do
create table(:reuser_improved_data) do
add(:dataset_id, references(:dataset, on_delete: :delete_all), null: false)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On pourrait retrouver le dataset_id avec la ressource mais je pense que ça facilitera bien le travail de le stocker ici.

add(:resource_id, references(:resource, on_delete: :delete_all), null: false)
add(:contact_id, references(:contact, on_delete: :delete_all), null: false)
add(:organization_id, references(:organization, type: :string, on_delete: :delete_all), null: false)
add(:download_url, :string, null: false)
timestamps(type: :utc_datetime_usec)
end

create(index(:reuser_improved_data, [:dataset_id]))
create(index(:reuser_improved_data, [:organization_id]))
create(unique_index(:reuser_improved_data, [:dataset_id, :organization_id]))
end
end
Loading