Skip to content

Commit

Permalink
feat: simplifier la collecte des destinataires des questions en atten…
Browse files Browse the repository at this point in the history
…te (#833)

## Description

🎸 Simplifier la génération de la liste des contacts destinataires de la
notification quotidienne sur le nombre de questions en attente

* supprimer la récupération des contacts depuis Brevo
* hydratation avec la liste des `users staff`

## Type de changement

🥁 Changement de rupture (modification ou caractéristique qui empêcherait
une fonctionnalité existante de fonctionner comme prévu) nécéssitant une
mise à jour de la documentation

### Points d'attention

🦺 refactor du test `SendNotifsOnUnansweredTopics` en pytest style
  • Loading branch information
vincentporte authored Nov 28, 2024
1 parent 6f03e8f commit 22eb8ca
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 294 deletions.
6 changes: 1 addition & 5 deletions clevercloud/cron.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,5 @@
"*/15 7-21 * * * $ROOT/clevercloud/send_notifications_regular.sh",
"20 6 * * * $ROOT/clevercloud/send_notifications_daily.sh",
"10 6-22 * * * $ROOT/clevercloud/add_user_to_list_when_register.sh",
"30 13 * * 1 $ROOT/clevercloud/send_notifs_on_unanswered_topics_list.sh 8",
"30 13 * * 2 $ROOT/clevercloud/send_notifs_on_unanswered_topics_list.sh 9",
"30 13 * * 3 $ROOT/clevercloud/send_notifs_on_unanswered_topics_list.sh 10",
"30 13 * * 4 $ROOT/clevercloud/send_notifs_on_unanswered_topics_list.sh 11",
"30 13 * * 5 $ROOT/clevercloud/send_notifs_on_unanswered_topics_list.sh 12"
"30 13 * * 1-5 $ROOT/clevercloud/send_notifs_on_unanswered_topics_list.sh"
]
6 changes: 1 addition & 5 deletions clevercloud/send_notifs_on_unanswered_topics_list.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
#!/bin/bash -l

if [ -z "$1" ]; then
echo "Error: Missing argument. Please provide List ID."
exit 1
fi

#
# About clever cloud cronjobs:
Expand All @@ -18,4 +14,4 @@ fi
# $APP_HOME is set by default by clever cloud.
cd $APP_HOME

python manage.py send_notifs_on_unanswered_topics --list_id $1
python manage.py send_notifs_on_unanswered_topics
1 change: 0 additions & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@
SIB_URL = os.getenv("SIB_URL", "http://test.com")
SIB_SMTP_URL = os.path.join(SIB_URL, "smtp/email")
SIB_CONTACTS_URL = os.path.join(SIB_URL, "contacts/import")
SIB_CONTACT_LIST_URL = os.path.join(SIB_URL, "contacts/lists")

SIB_API_KEY = os.getenv("SIB_API_KEY", "set-sib-api-key")
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "[email protected]")
Expand Down
151 changes: 3 additions & 148 deletions lacommunaute/forum_conversation/tests/__snapshots__/tests_views.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


<a class="matomo-event" data-matomo-action="view" data-matomo-category="engagement" data-matomo-option="member" href="/members/profile/poster_username/">
Alan T.
Adam O.
</a>,


Expand All @@ -23,7 +23,7 @@


<a class="matomo-event" data-matomo-action="view" data-matomo-category="engagement" data-matomo-option="member" href="/members/profile/poster_username/">
Dermot T.
Adam O.
</a>,
il y a 0 minute

Expand All @@ -37,158 +37,13 @@


<a class="matomo-event" data-matomo-action="view" data-matomo-category="engagement" data-matomo-option="member" href="/members/profile/poster_username/">
Jeff B.
Adam O.
</a>,
il y a 0 minute

</small>
'''
# ---
# name: TestTopicCreateCheckView.test_get_method[topic_create_check]
'''
<main class="s-main" id="main" role="main">





<section class="s-section" id="main_container">
<div class="s-section__container container">

<div class="row">
<div class="col-12">
<h1>forum</h1>
</div>
</div>
<div class="row c-box">
<div class="col-12 mb-5">
<p class="h2">Quelle est ma situation ?</p>
</div>
<div class="col-12 col-md-4 mb-3">
<div class="card c-card h-100">
<div class="card-header">
<h3 class="h4">J'utilise le site des emplois de l'inclusion</h3>
</div>
<div class="card-body pb-0">
<p>
J'ai des questions sur les PASS IAE, leur suspension ou prolongation, le fonctionnement du site des emplois de l'inclusion ou les relations avec l'ASP
</p>
</div>
<div class="card-footer text-center mt-3">
<a class="btn btn-primary matomo-event" data-matomo-action="topic-create-check" data-matomo-category="engagement" data-matomo-option="itou-helpdesk" href="https://aide.emplois.inclusion.beta.gouv.fr/hc/fr">J'accède à la documentation des emplois de l'inclusion</a>
</div>
</div>
</div>
<div class="col-12 col-md-4 mb-3">
<div class="card c-card h-100">
<div class="card-header">
<h3 class="h4">J'accueille des personnes en PMSMP</h3>
</div>
<div class="card-body pb-0">
<p>
J'ai une question Période de Mise en Situation en Milieu Professionnel, le conventionnement, la déclaration des heures ou le fonctionnement du site Immersion Facilitée
</p>
</div>
<div class="card-footer text-center">
<a class="btn btn-primary matomo-event" data-matomo-action="topic-create-check" data-matomo-category="engagement" data-matomo-option="pmsmp" href="https://aide.immersion-facile.beta.gouv.fr/fr/">
J'accède au centre d'aide Immersion Facilitée
</a>
</div>
</div>
</div>
<div class="col-12 col-md-4 mb-3">
<div class="card c-card h-100">
<div class="card-header">
<h3 class="h4">Je suis un/une professionnel/le de l'insertion, en formation ou en activité</h3>
</div>
<div class="card-body pb-0">
<p>
J'ai une question sur l'accompagnement d'une personne éloignée de l'emploi, sur mon parcours de formation ou sur une des fiches pratiques de la communauté
</p>
</div>
<div class="card-footer text-center">
<a class="btn btn-primary matomo-event" data-matomo-action="topic-create-check" data-matomo-category="engagement" data-matomo-option="forum" href="/forum/forum-[PK of Forum]/topic/create/?checked">Je pose ma question à la communauté</a>
</div>
</div>
</div>
</div>

</div>
</section>

</main>
'''
# ---
# name: TestTopicCreateView.test_redirections_on_documentation_forum[topic_create]
'''
<div class="form-group" id="div_id_content">


<label class="control-label" for="id_content">
Message

</label>


<textarea class="form-control" cols="40" id="id_content" name="content" placeholder="Entrez votre message" required="" rows="10"></textarea>






<small class="form-text text-muted">
<a aria-controls="collapseMentionsHelp" aria-expanded="false" data-bs-toggle="collapse" href="#collapseMentionsHelp" role="button">mention d'informations</a>
sur les champs libres
</small>
<div class="collapse" id="collapseMentionsHelp">
<div class="mb-3">
<p>
Nous vous demandons de ne pas nous transmettre d’informations sensibles. Notamment, ne communiquez pas vos opinions philosophiques, syndicales, politiques ou sur votre vie sexuelle. Ces données sont trop personnelles !
</p>
</div>
</div>



</div>
'''
# ---
# name: TestTopicCreateView.test_redirections_on_forum[topic_create]
'''
<div class="form-group" id="div_id_content">


<label class="control-label" for="id_content">
Message

</label>


<textarea class="form-control" cols="40" id="id_content" name="content" placeholder="Entrez votre message" required="" rows="10"></textarea>






<small class="form-text text-muted">
<a aria-controls="collapseMentionsHelp" aria-expanded="false" data-bs-toggle="collapse" href="#collapseMentionsHelp" role="button">mention d'informations</a>
sur les champs libres
</small>
<div class="collapse" id="collapseMentionsHelp">
<div class="mb-3">
<p>
Nous vous demandons de ne pas nous transmettre d’informations sensibles. Notamment, ne communiquez pas vos opinions philosophiques, syndicales, politiques ou sur votre vie sexuelle. Ces données sont trop personnelles !
</p>
</div>
</div>



</div>
'''
# ---
# name: TestTopicListView.test_clickable_tags[10-query_param1-clickable_tags_page2][clickable_tags_page2]
'<button class="tag bg-info-lighter text-info matomo-event" data-matomo-action="filter" data-matomo-category="engagement" data-matomo-option="topics" hx-get="/topics/?tag=tag&amp;filter=ALL" hx-push-url="true" hx-swap="outerHTML" hx-target="#topicsarea" id="filtertopics-button">tag</button>'
# ---
Expand Down
6 changes: 3 additions & 3 deletions lacommunaute/forum_conversation/tests/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ def test_filter_dropdown_with_tags(self, client, db, public_forum_with_topic, to

class TestPosterTemplate:
def test_topic_in_topics_view(self, client, db, topics_url, snapshot):
topic = TopicFactory(with_post=True, poster=UserFactory(first_name="Jeff", last_name="Buckley"))
topic = TopicFactory(with_post=True, poster=UserFactory(for_snapshot=True))
response = client.get(topics_url)
soup = parse_response_to_soup(
response, replace_in_href=[(topic.poster.username, "poster_username")], selector=".poster-infos"
Expand All @@ -990,7 +990,7 @@ def test_topic_from_other_public_forum_in_topics_view(self, client, db, topics_u
topic = TopicFactory(
with_post=True,
forum=ForumFactory(with_public_perms=True, name="Abby's Forum"),
poster=UserFactory(first_name="Alan", last_name="Turing"),
poster=UserFactory(for_snapshot=True),
)
response = client.get(topics_url)
soup = parse_response_to_soup(
Expand All @@ -1013,7 +1013,7 @@ def test_topic_in_its_own_public_forum(self, client, db, snapshot):
topic = TopicFactory(
with_post=True,
forum=ForumFactory(with_public_perms=True, name="Joe's Forum"),
poster=UserFactory(first_name="Dermot", last_name="Turing"),
poster=UserFactory(for_snapshot=True),
)
response = client.get(
reverse("forum_extension:forum", kwargs={"slug": topic.forum.slug, "pk": topic.forum.pk})
Expand Down
12 changes: 0 additions & 12 deletions lacommunaute/notification/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,3 @@ def bulk_send_user_to_list(users, list_id):
EmailSentTrack.objects.create(
status_code=response.status_code, response=response.text, datas=payload, kind=EmailSentTrackKind.ONBOARDING
)


def collect_users_from_list(list_id: int) -> list | None:
headers = {"api-key": settings.SIB_API_KEY, "Content-Type": "application/json", "Accept": "application/json"}
response = httpx.get(f"{settings.SIB_CONTACT_LIST_URL}/{list_id}/contacts", headers=headers)
if response.status_code == 200:
return [
{"email": contact["email"], "name": f'{contact["attributes"]["PRENOM"]} {contact["attributes"]["NOM"]}'}
for contact in response.json()["contacts"]
if not contact["emailBlacklisted"]
]
return None
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
class Command(BaseCommand):
help = "Envoyer une notification par email aux utilisateurs volontaires quand il y a des sujets sans réponse"

def add_arguments(self, parser):
parser.add_argument("--list_id", type=int, help="ID de la liste à utiliser")

def handle(self, *args, **options):
if "list_id" in options:
send_notifs_on_unanswered_topics(options["list_id"])
send_notifs_on_unanswered_topics()
self.stdout.write(self.style.SUCCESS("That's all, folks!"))
39 changes: 21 additions & 18 deletions lacommunaute/notification/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

from config.settings.base import DEFAULT_FROM_EMAIL, NEW_MESSAGES_EMAIL_MAX_PREVIEW, SIB_NEW_MESSAGES_TEMPLATE
from lacommunaute.forum_conversation.models import Topic
from lacommunaute.notification.emails import bulk_send_user_to_list, collect_users_from_list, send_email
from lacommunaute.forum_member.shortcuts import get_forum_member_display_name
from lacommunaute.notification.emails import bulk_send_user_to_list, send_email
from lacommunaute.notification.enums import EmailSentTrackKind, NotificationDelay
from lacommunaute.notification.models import Notification
from lacommunaute.notification.utils import collect_new_users_for_onboarding, get_serialized_messages
from lacommunaute.users.models import User


def send_messages_notifications(delay: NotificationDelay):
Expand Down Expand Up @@ -49,23 +51,24 @@ def add_user_to_list_when_register():
bulk_send_user_to_list(new_users, settings.SIB_ONBOARDING_LIST)


def send_notifs_on_unanswered_topics(list_id: int) -> None:
contacts = collect_users_from_list(list_id)
def send_notifs_on_unanswered_topics() -> None:
contacts = User.objects.filter(is_staff=True)
count = Topic.objects.unanswered().count()

if contacts:
count = Topic.objects.unanswered().count()
link = (
f"{settings.COMMU_PROTOCOL}://{settings.COMMU_FQDN}",
reverse("forum_conversation_extension:topics"),
"?filter=NEW&mtm_campaign=unsanswered&mtm_medium=email#community",
)
if not contacts.exists() or count == 0:
return None

params = {"count": count, "link": "".join(link)}
contacts_list = [{"email": contact.email, "name": get_forum_member_display_name(contact)} for contact in contacts]
link = (
f"{settings.COMMU_PROTOCOL}://{settings.COMMU_FQDN}",
reverse("forum_conversation_extension:topics"),
"?filter=NEW&mtm_campaign=unsanswered&mtm_medium=email#community",
)
params = {"count": count, "link": "".join(link)}

if count > 0:
send_email(
to=contacts,
params=params,
template_id=settings.SIB_UNANSWERED_QUESTION_TEMPLATE,
kind=EmailSentTrackKind.PENDING_TOPIC,
)
send_email(
to=contacts_list,
params=params,
template_id=settings.SIB_UNANSWERED_QUESTION_TEMPLATE,
kind=EmailSentTrackKind.PENDING_TOPIC,
)
46 changes: 2 additions & 44 deletions lacommunaute/notification/tests/tests_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from django.test import TestCase
from faker import Faker

from config.settings.base import DEFAULT_FROM_EMAIL, SIB_CONTACT_LIST_URL, SIB_CONTACTS_URL, SIB_SMTP_URL
from lacommunaute.notification.emails import bulk_send_user_to_list, collect_users_from_list, send_email
from config.settings.base import DEFAULT_FROM_EMAIL, SIB_CONTACTS_URL, SIB_SMTP_URL
from lacommunaute.notification.emails import bulk_send_user_to_list, send_email
from lacommunaute.notification.models import EmailSentTrack
from lacommunaute.users.factories import UserFactory

Expand Down Expand Up @@ -79,45 +79,3 @@ def test_bulk_send_user_to_list(self):
self.assertEqual(email_sent_track.response, json.dumps({"message": "OK"}))
self.assertEqual(email_sent_track.datas, payload)
self.assertEqual(email_sent_track.kind, "onboarding")


class CollectUsersFromListTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.contact_list_response = {
"contacts": [
{
"email": faker.email(),
"emailBlacklisted": False,
"attributes": {"PRENOM": faker.first_name(), "NOM": faker.name()},
},
{
"email": faker.email(),
"emailBlacklisted": True,
"attributes": {"PRENOM": faker.first_name(), "NOM": faker.name()},
},
]
}
respx.get(SIB_CONTACT_LIST_URL + "/1/contacts").mock(
return_value=httpx.Response(200, json=cls.contact_list_response)
)

@respx.mock
def test_collect_users_from_list_bad_status_code(self):
list_id = faker.random_int()
respx.get(SIB_CONTACT_LIST_URL + f"/{list_id}/contacts").mock(return_value=httpx.Response(500))
self.assertIsNone(collect_users_from_list(list_id))

@respx.mock
def test_collect_users_from_list(self):
expected_contact = self.contact_list_response["contacts"][0]
contacts = collect_users_from_list(1)
self.assertEqual(
contacts,
[
{
"email": expected_contact["email"],
"name": f'{expected_contact["attributes"]["PRENOM"]} {expected_contact["attributes"]["NOM"]}',
}
],
)
Loading

0 comments on commit 22eb8ca

Please sign in to comment.