Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoineAugusti committed Oct 3, 2024
1 parent 1b5802e commit 26b5e57
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 0 deletions.
8 changes: 8 additions & 0 deletions apps/transport/lib/transport/notification_reason.ex
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ defmodule Transport.NotificationReason do
|> Map.keys()
end

@spec subscribable_reasons_for_role(role()) :: [reason()]
def subscribable_reasons_for_role(role) do
reasons_for_role(role)
|> MapSet.new()
|> MapSet.intersection(MapSet.new(subscribable_reasons()))
|> MapSet.to_list()
end

@doc """
iex> hidden_reasons_for_role(:reuser)
[:datasets_switching_climate_resilience_bill]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,118 @@ defmodule TransportWeb.Backoffice.ContactController do
|> redirect(to: backoffice_contact_path(conn, :index))
end

def csv_export(%Plug.Conn{} = conn, _params) do
filename = "contacts-#{Date.utc_today() |> Date.to_iso8601()}.csv"

query = """
select *
from contact
join (
select
c.id contact_id,
count(d.id) > 0 is_producer,
count(df.id) > 0 is_reuser,
array_agg(distinct o.name) organization_names,
array_agg(distinct ns_producer.reason) producer_reasons,
array_agg(distinct ns_reuser.reason) reuser_reasons
from contact c
left join contacts_organizations co on co.contact_id = c.id
left join organization o on o.id = co.organization_id
left join dataset d on d.organization_id = co.organization_id
left join dataset_followers df on df.contact_id = c.id
left join notification_subscription ns_producer on ns_producer.contact_id = c.id and ns_producer.role = 'producer'
left join notification_subscription ns_reuser on ns_reuser.contact_id = c.id and ns_reuser.role = 'reuser'
group by 1
) t on t.contact_id = id
"""

csv_header =
[
"id",
"first_name",
"last_name",
"mailing_list_title",
"email",
"phone_number",
"job_title",
"organization",
"inserted_at",
"updated_at",
"datagouv_user_id",
"last_login_at",
"creation_source",
"organization_names",
columns_for_role(:producer),
columns_for_role(:reuser)
]
|> List.flatten()

# Stream the query from the database and send 100 rows at a time
{:ok, conn} =
DB.Repo.transaction(
fn ->
Ecto.Adapters.SQL.stream(DB.Repo, query)
|> Stream.map(fn %Postgrex.Result{rows: rows, columns: columns} ->
Enum.map(rows, fn row ->
build_csv_row(csv_header, Enum.zip(columns, row) |> Enum.into(%{}))
end)
end)
|> send_csv_response(filename, csv_header, conn)
end,
timeout: :timer.seconds(60)
)

conn
end

defp columns_for_role(role) do
["is_#{role}" | Enum.map(Transport.NotificationReason.subscribable_reasons_for_role(role), &"#{role}_#{&1}")]
end

defp send_csv_response(chunks, filename, csv_header, %Plug.Conn{} = conn) do
{:ok, conn} =
conn
|> put_resp_content_type("text/csv")
|> put_resp_header("content-disposition", ~s|attachment; filename="#{filename}"|)
|> send_chunked(:ok)
|> send_csv_chunk([csv_header])

Enum.reduce_while(chunks, conn, fn data, conn ->
case send_csv_chunk(conn, data) do
{:ok, conn} -> {:cont, conn}
{:error, :closed} -> {:halt, conn}
end
end)
end

defp send_csv_chunk(%Plug.Conn{} = conn, data) do
chunk(conn, data |> NimbleCSV.RFC4180.dump_to_iodata())
end

defp build_csv_row(csv_header, row) do
row =
row
|> Map.update!("email", &Transport.Vault.decrypt!/1)
|> add_roles_columns()

# Build a row following same order as the CSV header
Enum.map(csv_header, &Map.fetch!(row, &1))
end

defp add_roles_columns(%{"producer_reasons" => producer_reasons, "reuser_reasons" => reuser_reasons} = row) do
roles_columns =
Map.new(Transport.NotificationReason.subscribable_reasons_for_role(:producer), fn reason ->
{"producer_#{reason}", to_string(reason) in producer_reasons}
end)
|> Map.merge(
Map.new(Transport.NotificationReason.subscribable_reasons_for_role(:reuser), fn reason ->
{"reuser_#{reason}", to_string(reason) in reuser_reasons}
end)
)

Map.merge(row, roles_columns)
end

defp render_form(%Plug.Conn{assigns: assigns} = conn) do
contact_id = Map.get(assigns, :contact_id)

Expand Down
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 @@ -173,6 +173,7 @@ defmodule TransportWeb.Router do
scope "/contacts" do
get("/", ContactController, :index)
get("/new", ContactController, :new)
get("/csv_export", ContactController, :csv_export)
post("/create", ContactController, :create)
get("/:id/edit", ContactController, :edit)
post("/:id/delete", ContactController, :delete)
Expand Down

0 comments on commit 26b5e57

Please sign in to comment.