Repository: fossasia/open-event-server

Issue:
After-Event Email: Several identical mails sent out, if user has several sessions/roles
If a user has several sessions and roles, e.g. speaker and event organizer, several after-event emails are sent out. Expected: Only send one email per email address.

![Screenshot from 2020-01-08 11-02-59](https://user-images.githubusercontent.com/1583873/71952996-c8e70f80-3206-11ea-916f-f9e332d70faf.png)



Discussion:
@iamareebjamal server issue
Bug in APS, to be replaced by celery beat. Every gunicorn worker including celery runs a copy of APS, and hence everything is repeated 5 times (4 workers + 1 celery)
APS: Python Scheduler ? I will be looking into it.
Taking this up! 👍 

> APS: Python Scheduler ? I will be looking into it.

Also there are already 3-4 PR opened by you which are not merged. Please work on them before making more new one's.
> Also there are already 3-4 PR opened by you which are not merged. Please work on them before making more new one's.

It has been 3 days and no one was working and the priority is HIGH also.
@kushthedude If you are working on this, then no problem. Also yes they are in the pending state due to the solution being searched simultaneously as I m stuck in both and in sync with Areeb (You can see the conversation). And if you are not working on this, then I can send the patch, why not move the project forward simultaneously. Please tell what should be done?

Thanks.
There are a lot of issues already present,needless to say you should give
atleast a week to other dev before claiming the issue if they are working
on it. If you dont hear from them a week or dont see a patch you should
definitely go ahead after commenting.
Each contributor should get an equal opportunity for contribution whether
it be me, you or any new comer.
Anyway, If you don't have issue to work on, you can go ahead with it. No
issues 😄

On Fri, 10 Jan, 2020, 22:20 Suneet Srivastava, <notifications@github.com>
wrote:

> Also there are already 3-4 PR opened by you which are not merged. Please
> work on them before making more new one's.
>
> It has been 3 days and no one was working and the priority is HIGH also.
> @kushthedude <https://github.com/kushthedude> If you are working on this,
> then no problem. Also yes they are in the pending state due to the solution
> being searched simultaneously as I m stuck in both and in sync with Areeb
> (You can see the conversation). And if you are not working on this, then I
> can send the patch, why not move the project forward simultaneously. Please
> tell what should be done?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/fossasia/open-event-server/issues/6731?email_source=notifications&email_token=AKQMTLSD5G3ZLZAAWPSSZT3Q5CRMPA5CNFSM4KEER33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIUQSKQ#issuecomment-573114666>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AKQMTLTXERWZOAYMVUWWLXDQ5CRMPANCNFSM4KEER33A>
> .
>

@codedsun Are you working on it?
No, @vishakhanihore You may ask @kushthedude if he is working on it.
Sure go ahead.

On Sat, 11 Jan, 2020, 09:48 Suneet Srivastava, <notifications@github.com>
wrote:

> No, @vishakhanihore <https://github.com/vishakhanihore> You may ask
> @kushthedude <https://github.com/kushthedude> if he is working on it.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/fossasia/open-event-server/issues/6731?email_source=notifications&email_token=AKQMTLXVPYDKWTVLRBZFE4DQ5FB77A5CNFSM4KEER33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIVY2RY#issuecomment-573279559>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AKQMTLVZKMQUEXAME3RPH6TQ5FB77ANCNFSM4KEER33A>
> .
>

@vishakhanihore - Are you still working on this?
Didnt get time to finish it. You may go ahead if you want.

On Sun, 19 Jan, 2020, 18:35 Suneet Srivastava, <notifications@github.com>
wrote:

> @vishakhanihore <https://github.com/vishakhanihore> - Are you still
> working on this?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/fossasia/open-event-server/issues/6731?email_source=notifications&email_token=AM6PS4WDCABD4SW6FFVBRP3Q6RFZBA5CNFSM4KEER33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJKRUZQ#issuecomment-576002662>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AM6PS4WMNTIMMBWNXQPQI5DQ6RFZBANCNFSM4KEER33A>
> .
>

@iamareebjamal - In this, all the scheduled jobs will become a celery task and then be scheduled by celery beat. correct?
yes
When sending mail function is called, it sends to all speaker, organiser, owner irrespective of if it has already been sent to one.
@kushthedude - Yes, but it runs after every 5:30 mins. Can you point more clearly what you want to say.

Also, @iamareebjamal mentioned that it was due to the gununicorn workers(4 in numbers) which were causing this issue, that's why we replaced the APS with celery beat.
> @kushthedude - Yes, but it runs after every 5:30 mins. Can you point more clearly what you want to say.

I didn't know why you even mentioned this.

See this snippet
```py
 if current_time > event.ends_at and time_difference_minutes < 1440:
                for speaker in speakers:
                    if not speaker.is_email_overridden:
                        send_email_after_event(speaker.user.email, event.name, frontend_url)
                        send_notif_after_event(speaker.user, event.name)
                for organizer in organizers:
                    send_email_after_event(organizer.user.email, event.name, frontend_url)
                    send_notif_after_event(organizer.user, event.name)
                if owner:
                    send_email_after_event(owner.user.email, event.name, frontend_url)
                    send_notif_after_event(owner.user, event.name)
```



> > @kushthedude - Yes, but it runs after every 5:30 mins. Can you point more clearly what you want to say.
> 
> I didn't know why you even mentioned this.
> 
> See this snippet
> 
> ```python
>  if current_time > event.ends_at and time_difference_minutes < 1440:
>                 for speaker in speakers:
>                     if not speaker.is_email_overridden:
>                         send_email_after_event(speaker.user.email, event.name, frontend_url)
>                         send_notif_after_event(speaker.user, event.name)
>                 for organizer in organizers:
>                     send_email_after_event(organizer.user.email, event.name, frontend_url)
>                     send_notif_after_event(organizer.user, event.name)
>                 if owner:
>                     send_email_after_event(owner.user.email, event.name, frontend_url)
>                     send_notif_after_event(owner.user, event.name)
> ```

@iamareebjamal What should be the optimal approach for this, A bunch of conditionals with flag variable or something else?
Extract all emails in a set and loop over them to send the email 
There is more this issue, being ab owner and Speaker of a event. I just
received 15 after event mail

On Fri, 31 Jan, 2020, 06:14 Areeb Jamal, <notifications@github.com> wrote:

> Extract all emails in a set and loop over them to send the email
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/fossasia/open-event-server/issues/6731?email_source=notifications&email_token=AKQMTLTOKWTU24CV2MRMBZ3RANX6RA5CNFSM4KEER33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKNC2UA#issuecomment-580529488>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AKQMTLR2IVK2RXJ6MCOC2STRANX6RANCNFSM4KEER33A>
> .
>


Resolving patch:
diff --git a/app/api/helpers/scheduled_jobs.py b/app/api/helpers/scheduled_jobs.py
--- a/app/api/helpers/scheduled_jobs.py
+++ b/app/api/helpers/scheduled_jobs.py
@@ -21,7 +21,7 @@
 )
 from app.api.helpers.query import get_user_event_roles_by_role_name
 from app.api.helpers.storage import UPLOAD_PATHS
-from app.api.helpers.utilities import monthdelta
+from app.api.helpers.utilities import monthdelta, make_dict
 from app.instance import celery
 from app.models import db
 from app.models.event import Event
@@ -53,18 +53,24 @@ def send_after_event_mail():
             )
             frontend_url = get_settings()['frontend_url']
             if current_time > event.ends_at and time_difference_minutes < 1440:
+                unique_emails = set()
+                user_objects = []
                 for speaker in speakers:
                     if not speaker.is_email_overridden:
-                        send_email_after_event(
-                            speaker.user.email, event.name, frontend_url
-                        )
-                        send_notif_after_event(speaker.user, event.name)
+                        unique_emails.add(speaker.user.email)
+                        user_objects.append(speaker.user)
                 for organizer in organizers:
-                    send_email_after_event(organizer.user.email, event.name, frontend_url)
-                    send_notif_after_event(organizer.user, event.name)
+                    unique_emails.add(organizer.user.email)
+                    user_objects.append(organizer.user)
                 if owner:
-                    send_email_after_event(owner.user.email, event.name, frontend_url)
-                    send_notif_after_event(owner.user, event.name)
+                    unique_emails.add(owner.user.email)
+                    user_objects.append(owner.user)
+                for email in unique_emails:
+                    send_email_after_event(email, event.name, frontend_url)
+                # Unique user's dict based on their id.
+                unique_users_dict = make_dict(user_objects, "id")
+                for user in unique_users_dict.values():
+                    send_notif_after_event(user, event.name)
 
 
 @celery.task(base=RequestContextTask, name='change.session.state.on.event.completion')
diff --git a/app/api/helpers/utilities.py b/app/api/helpers/utilities.py
--- a/app/api/helpers/utilities.py
+++ b/app/api/helpers/utilities.py
@@ -13,6 +13,19 @@
 from app.api.helpers.errors import UnprocessableEntityError
 
 
+def make_dict(list_of_object, key):
+    """
+    To convert a list of object into dict.
+
+    This will return a dict containing unique keys
+    mapped to the object which contains that key.
+    """
+    mapped_dict = {}
+    for obj in list_of_object:
+        mapped_dict[getattr(obj, key)] = obj
+    return mapped_dict
+
+
 def dasherize(text):
     return text.replace('_', '-')
 


Test patch: