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

Push notification messages #181

Merged
merged 17 commits into from
Nov 8, 2023
Merged

Push notification messages #181

merged 17 commits into from
Nov 8, 2023

Conversation

pxwxnvermx
Copy link
Contributor

@pxwxnvermx pxwxnvermx force-pushed the pkv/push-notification-messages branch from 21bb688 to d5bea57 Compare October 25, 2023 08:53
@pxwxnvermx pxwxnvermx self-assigned this Oct 25, 2023
@pxwxnvermx pxwxnvermx marked this pull request as ready for review November 1, 2023 13:43
@pxwxnvermx pxwxnvermx force-pushed the pkv/push-notification-messages branch from 54d55eb to c8057dc Compare November 1, 2023 13:44
Copy link
Collaborator

@calellowitz calellowitz left a comment

Choose a reason for hiding this comment

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

Few questions. Overall code looks fine, just curious about various requirements mostly

@celery_app.task()
def send_notification_inactive_users(opportunity_id: int):
opportunity = Opportunity.objects.get(opportunity_id=opportunity_id)
opportunity_accesses = OpportunityAccess.objects.filter(opportunity_id=opportunity_id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can do this in one line. You can access the Opportunity object from the OpportunityAccess one with opportunity_access.opportunity and you can have it fetch both in one query with

OpportunityAccess.objects.filter(opportunity_id=opportunity_id).select_related("opportunity")

)
has_started_deliver = OpportunityClaim.objects.exists(access=access)
if (
last_user_learn_module.date <= datetime.date.today() - datetime.timedelta(days=3)
Copy link
Collaborator

Choose a reason for hiding this comment

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

where does 3 come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It comes from the mobile user stories doc.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe make these constants. You could also make a small helper:

date_before(last_user_learn_module.date, TWO_DAYS)
# or
date_before(last_user_learn_module.date, days=2)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made a helper to check for dates. 0e83ab2

UserVisit.objects.filter(user=access.user, opportunity_id=opportunity_id).order_by("visit_date").last()
)
if (
last_user_deliver_visit.visit_date <= datetime.date.today() - datetime.timedelta(days=2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

where does 2 come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It comes fromthe mobile user stories doc.

title=gettext(f"Resume your job for {opportunity.name}"),
body=gettext(
f"You have not completed your delivery visits for {opportunity.name}."
"Please complete all the deliver visits to avail the payment."
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there somewhere these messages are coming from. The language is little harsh

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The body of the messages comes from me, that could be the reason they look a little unrefined. Any suggestions on how to improved these messages?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe:

All deliver visits must be completed before payment can be released.

Not sure if deliver visits is the correct term. Also I thought there was payment along the way and not just at the end?

Option 2:

To maximise your payout complete all the required service delivery.

@@ -86,6 +89,14 @@ def get_form_kwargs(self):
def form_valid(self, form: OpportunityCreationForm) -> HttpResponse:
response = super().form_valid(form)
create_learn_modules_and_deliver_units.delay(self.object.id)
interval = IntervalSchedule.objects.get_or_create(every=1, period=IntervalSchedule.DAYS)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this the recommended way to do periodic tasks? I would have imagined it was simpler to make one task and have it iterate over all opportunities, but I am not sure how to schedule that. In HQ we would use the @periodic_task decorator and give it a schedule. The other advantage of one task rather than one per domain is that it could quickly end up a very large number of tasks and clog celery.

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 for the one task approach. You can create the schedule with a Django migration or using the post_migrate signal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a migration to create the periodic task. b79b62c

@pxwxnvermx pxwxnvermx requested a review from snopoke November 2, 2023 04:24
@@ -86,6 +89,14 @@ def get_form_kwargs(self):
def form_valid(self, form: OpportunityCreationForm) -> HttpResponse:
response = super().form_valid(form)
create_learn_modules_and_deliver_units.delay(self.object.id)
interval = IntervalSchedule.objects.get_or_create(every=1, period=IntervalSchedule.DAYS)
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 for the one task approach. You can create the schedule with a Django migration or using the post_migrate signal.

commcare_connect/opportunity/tasks.py Outdated Show resolved Hide resolved
commcare_connect/opportunity/tasks.py Outdated Show resolved Hide resolved
title=gettext(f"Resume your job for {opportunity.name}"),
body=gettext(
f"You have not completed your delivery visits for {opportunity.name}."
"Please complete all the deliver visits to avail the payment."
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe:

All deliver visits must be completed before payment can be released.

Not sure if deliver visits is the correct term. Also I thought there was payment along the way and not just at the end?

Option 2:

To maximise your payout complete all the required service delivery.

)
has_started_deliver = OpportunityClaim.objects.exists(access=access)
if (
last_user_learn_module.date <= datetime.date.today() - datetime.timedelta(days=3)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe make these constants. You could also make a small helper:

date_before(last_user_learn_module.date, TWO_DAYS)
# or
date_before(last_user_learn_module.date, days=2)

usernames=[access.user.username],
title=gettext(f"Resume your learning journey for {opportunity.name}"),
body=gettext(
f"You have not completed your learning for {opportunity.name}."
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if they have completed all the modules but haven't yet claimed the opportunity? I think we're only checking when the last module was done and not whether they have done them all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

has_started_deliver indicates that if the Opportunity Claim exists or not. I have also added this check in both of the if blocks. If user has a opportunity claim then we dont need to check the learn modules.

Copy link
Collaborator

Choose a reason for hiding this comment

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

True, but it could also be the case that the user has completed the learning modules but has not yet claimed the opportunity right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this case, and refactored the code to make testing easier. f629e9e

Copy link
Collaborator

@snopoke snopoke left a comment

Choose a reason for hiding this comment

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

Few small comments but otherwise looking good.



def create_send_inactive_notification_periodic_task(apps, schema_editor):
interval, _ = IntervalSchedule.objects.get_or_create(every=1, period=IntervalSchedule.DAYS)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just thought of this now. I think it would be better to use a cron schedule so that it fires at a consistent time each day, probably around 7am UTC I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. 471037d

message = _get_deliver_message(access)
else:
message = _get_learn_message(access)
if isinstance(message, Message):
Copy link
Collaborator

Choose a reason for hiding this comment

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

The possible types for message are either Message or None so I think it would be clearer to use if messages otherwise it gives the impression that there may be other types being returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. f629e9e

commcare_connect/opportunity/visit_import.py Show resolved Hide resolved
@pxwxnvermx pxwxnvermx merged commit 7a0458c into main Nov 8, 2023
2 checks passed
@pxwxnvermx pxwxnvermx deleted the pkv/push-notification-messages branch November 8, 2023 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants