-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ajout d'une notification quand les ressources ont changé (#3347)
* Send a notification when resources changed * Add translation
- Loading branch information
1 parent
268c4ea
commit 10ea7e0
Showing
7 changed files
with
274 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
apps/transport/lib/jobs/resources_changed_notification_job.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
defmodule Transport.Jobs.ResourcesChangedNotificationJob do | ||
@moduledoc """ | ||
Job in charge of detecting datasets for which resources changed: | ||
- a resource has been added | ||
- a resource has been deleted | ||
- a download URL changed | ||
and notifying subscribers about this. | ||
""" | ||
use Oban.Worker, max_attempts: 3, tags: ["notifications"] | ||
import Ecto.Query | ||
@notification_reason :resources_changed | ||
|
||
@impl Oban.Worker | ||
def perform(%Oban.Job{args: args}) when is_nil(args) or args == %{} do | ||
relevant_datasets() | ||
|> Enum.map(&new/1) | ||
|> Oban.insert_all() | ||
|
||
:ok | ||
end | ||
|
||
@impl Oban.Worker | ||
def perform(%Oban.Job{args: %{"dataset_id" => dataset_id}}) do | ||
dataset = DB.Dataset |> DB.Repo.get!(dataset_id) | ||
subject = "#{dataset.custom_title} : ressources modifiées" | ||
|
||
@notification_reason | ||
|> DB.NotificationSubscription.subscriptions_for_reason() | ||
|> DB.NotificationSubscription.subscriptions_to_emails() | ||
|> Enum.each(fn email -> | ||
Transport.EmailSender.impl().send_mail( | ||
"transport.data.gouv.fr", | ||
Application.get_env(:transport, :contact_email), | ||
email, | ||
Application.get_env(:transport, :contact_email), | ||
subject, | ||
"", | ||
Phoenix.View.render_to_string(TransportWeb.EmailView, "resources_changed.html", dataset: dataset) | ||
) | ||
|
||
save_notification(dataset, email) | ||
end) | ||
end | ||
|
||
def save_notification(%DB.Dataset{} = dataset, email) do | ||
DB.Notification.insert!(@notification_reason, dataset, email) | ||
end | ||
|
||
def relevant_datasets do | ||
today = Date.utc_today() | ||
|
||
# Latest `dataset_history` by dataset by day | ||
# (usually 1 row per day but make sure duplicates are handled) | ||
history_by_day_sub = | ||
DB.DatasetHistory | ||
|> select([d], max(d.id)) | ||
|> group_by([d], [d.dataset_id, fragment("?::date", d.inserted_at)]) | ||
|
||
urls_by_day_sub = | ||
DB.DatasetHistory | ||
|> join(:inner, [dh], dhr in DB.DatasetHistoryResources, on: dh.id == dhr.dataset_history_id) | ||
|> join(:inner, [_dh, dhr], r in DB.Resource, on: r.id == dhr.resource_id and not r.is_community_resource) | ||
|> where([dh, _dhr, _r], dh.id in subquery(history_by_day_sub)) | ||
|> group_by([dh, _dhr, _r], [dh.dataset_id, fragment("?::date", dh.inserted_at)]) | ||
|> select([dh, dhr, _d], %{ | ||
dataset_id: dh.dataset_id, | ||
date: fragment("?::date", dh.inserted_at), | ||
urls: fragment("string_agg(?->>'download_url', ',' order by ?->>'download_url')", dhr.payload, dhr.payload) | ||
}) | ||
|
||
base = from(today in subquery(urls_by_day_sub)) | ||
|
||
base | ||
|> join(:inner, [today], yesterday in subquery(urls_by_day_sub), | ||
on: | ||
today.dataset_id == yesterday.dataset_id and | ||
yesterday.date == fragment("? - 1", today.date) and | ||
today.urls != yesterday.urls | ||
) | ||
|> where([today, _yesterday], today.date == ^today) | ||
|> DB.Repo.all() | ||
end | ||
end |
5 changes: 5 additions & 0 deletions
5
apps/transport/lib/transport_web/templates/email/resources_changed.html.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Bonjour, | ||
|
||
Les ressources du jeu de données <%= link_for_dataset(@dataset, :heex) %> viennent d'être modifiées (ajout/suppression de ressources ou modification d'URLs de téléchargement). | ||
|
||
L’équipe du PAN |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
171 changes: 171 additions & 0 deletions
171
apps/transport/test/transport/jobs/resources_changed_notification_job_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
defmodule Transport.Test.Transport.Jobs.ResourcesChangedNotificationJobTest do | ||
use ExUnit.Case, async: true | ||
import DB.Factory | ||
import Mox | ||
use Oban.Testing, repo: DB.Repo | ||
alias Transport.Jobs.ResourcesChangedNotificationJob | ||
|
||
setup do | ||
Ecto.Adapters.SQL.Sandbox.checkout(DB.Repo) | ||
end | ||
|
||
setup :verify_on_exit! | ||
|
||
test "relevant_datasets and dispatches jobs" do | ||
today = DateTime.utc_now() | ||
today_date = today |> DateTime.to_date() | ||
yesterday = today |> DateTime.add(-1, :day) | ||
day_before = today |> DateTime.add(-2, :day) | ||
|
||
%DB.Dataset{id: dataset_id} = dataset = insert(:dataset) | ||
other_dataset = insert(:dataset) | ||
|
||
resource_dataset_gtfs = | ||
insert(:resource, | ||
dataset: dataset, | ||
format: "GTFS", | ||
url: "https://example.com/gtfs.zip", | ||
is_community_resource: false | ||
) | ||
|
||
resource_dataset_gtfs_rt = | ||
insert(:resource, | ||
dataset: dataset, | ||
format: "gtfs-rt", | ||
url: "https://example.com/gtfs-rt", | ||
is_community_resource: false | ||
) | ||
|
||
resource_other_dataset_gtfs = | ||
insert(:resource, | ||
dataset: other_dataset, | ||
format: "GTFS", | ||
is_community_resource: false, | ||
url: "https://example.fr/gtfs.zip" | ||
) | ||
|
||
community_resource_other_dataset = | ||
insert(:resource, | ||
dataset: other_dataset, | ||
format: "GTFS", | ||
is_community_resource: true, | ||
url: "https://lemonde.fr/pan" | ||
) | ||
|
||
resource_other_dataset_gtfs_rt = | ||
insert(:resource, | ||
dataset: other_dataset, | ||
format: "gtfs-rt", | ||
is_community_resource: false, | ||
url: "https://example.fr/gtfs.zip" | ||
) | ||
|
||
# `dataset` has a new resource today, `other_dataset` did not change today but before | ||
dataset_dh_today = insert(:dataset_history, dataset: dataset, inserted_at: today) | ||
dataset_dh_yesterday = insert(:dataset_history, dataset: dataset, inserted_at: yesterday) | ||
other_dataset_dh_today = insert(:dataset_history, dataset: other_dataset, inserted_at: today) | ||
other_dataset_dh_yesterday = insert(:dataset_history, dataset: other_dataset, inserted_at: yesterday) | ||
other_dataset_dh_day_before = insert(:dataset_history, dataset: other_dataset, inserted_at: day_before) | ||
|
||
# For `dataset` | ||
# Today: GTFS-RT + GTFS | ||
# Yesterday: GTFS | ||
insert(:dataset_history_resources, | ||
resource: resource_dataset_gtfs, | ||
payload: %{"download_url" => resource_dataset_gtfs.url}, | ||
dataset_history: dataset_dh_today | ||
) | ||
|
||
insert(:dataset_history_resources, | ||
resource: resource_dataset_gtfs_rt, | ||
payload: %{"download_url" => resource_dataset_gtfs_rt.url}, | ||
dataset_history: dataset_dh_today | ||
) | ||
|
||
insert(:dataset_history_resources, | ||
resource: resource_dataset_gtfs, | ||
payload: %{"download_url" => resource_dataset_gtfs.url}, | ||
dataset_history: dataset_dh_yesterday | ||
) | ||
|
||
# For `other_dataset` | ||
# - 2 days ago: GTFS-RT + GTFS (should be ignored) | ||
# - Only a GTFS for yesterday and today | ||
# - A new community resource was added today (should be ignored) | ||
insert(:dataset_history_resources, | ||
resource: resource_other_dataset_gtfs, | ||
payload: %{"download_url" => resource_other_dataset_gtfs.url}, | ||
dataset_history: other_dataset_dh_today | ||
) | ||
|
||
insert(:dataset_history_resources, | ||
resource: community_resource_other_dataset, | ||
payload: %{"download_url" => community_resource_other_dataset.url}, | ||
dataset_history: other_dataset_dh_today | ||
) | ||
|
||
insert(:dataset_history_resources, | ||
resource: resource_other_dataset_gtfs, | ||
payload: %{"download_url" => resource_other_dataset_gtfs.url}, | ||
dataset_history: other_dataset_dh_yesterday | ||
) | ||
|
||
insert(:dataset_history_resources, | ||
resource: resource_other_dataset_gtfs, | ||
payload: %{"download_url" => resource_other_dataset_gtfs.url}, | ||
dataset_history: other_dataset_dh_day_before | ||
) | ||
|
||
insert(:dataset_history_resources, | ||
resource: resource_other_dataset_gtfs_rt, | ||
payload: %{"download_url" => resource_other_dataset_gtfs_rt.url}, | ||
dataset_history: other_dataset_dh_day_before | ||
) | ||
|
||
assert [ | ||
%{ | ||
dataset_id: ^dataset_id, | ||
date: ^today_date, | ||
urls: "https://example.com/gtfs-rt,https://example.com/gtfs.zip" | ||
} | ||
] = ResourcesChangedNotificationJob.relevant_datasets() | ||
|
||
assert :ok == perform_job(ResourcesChangedNotificationJob, %{}) | ||
|
||
assert [%Oban.Job{worker: "Transport.Jobs.ResourcesChangedNotificationJob", args: %{"dataset_id" => ^dataset_id}}] = | ||
all_enqueued() | ||
end | ||
|
||
test "perform with a dataset_id" do | ||
%DB.Dataset{id: dataset_id} = dataset = insert(:dataset, custom_title: "Super JDD") | ||
%DB.Contact{id: contact_id, email: email} = insert_contact() | ||
|
||
insert(:notification_subscription, %{ | ||
reason: :resources_changed, | ||
source: :admin, | ||
role: :reuser, | ||
contact_id: contact_id | ||
}) | ||
|
||
Transport.EmailSender.Mock | ||
|> expect(:send_mail, fn "transport.data.gouv.fr", | ||
"[email protected]", | ||
^email, | ||
"[email protected]", | ||
"Super JDD : ressources modifiées" = _subject, | ||
"", | ||
html_part -> | ||
assert html_part =~ | ||
~s(Les ressources du jeu de données <a href="http://127.0.0.1:5100/datasets/#{dataset.slug}">#{dataset.custom_title}</a> viennent d’être modifiées) | ||
|
||
:ok | ||
end) | ||
|
||
assert :ok == perform_job(ResourcesChangedNotificationJob, %{"dataset_id" => dataset_id}) | ||
|
||
# Logs have been saved | ||
assert [ | ||
%DB.Notification{email: ^email, reason: :resources_changed, dataset_id: ^dataset_id} | ||
] = DB.Notification |> DB.Repo.all() | ||
end | ||
end |