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

Added notification emails and basic scheduling logic #648

Open
wants to merge 80 commits into
base: trunk
Choose a base branch
from

Conversation

peterfabian
Copy link

@peterfabian peterfabian commented Aug 5, 2024

First POC that contributes towards #645

Description

This is a WIP to add notifications to Subscriptions Core.

Currently, it

  • adds simple email templates (placeholders until we figure out the specifics),
  • adds email-related classes,
  • registers these classes,
  • adds simple scheduling logic for actions that should trigger the email with an offset (3 days)

Mostly, it's inspired by code that's already in Subscriptions Core.

Notification Types Added

  • Auto Renewal
    • scheduled {{time_offset}} ahead of $subscription->get_date( 'next_payment' ) for automatically renewing subscriptions ($subscription->is_manual() is false)
  • Manual Renewal
    • scheduled {{time_offset}} ahead of $subscription->get_date( 'next_payment' ) manual subscriptions ($subscription->is_manual() is true)
  • Trial Expiry
    • scheduled {{time_offset}} ahead of $subscription->get_date( 'trial_end' ) manual subscriptions
  • Subscription End
    • scheduled {{time_offset}} ahead of $subscription->get_date( 'end' ) manual subscriptions

Open Questions

Some questions that crossed my mind while implementing this:

  • We need to decide where to put filters/actions to allow extensibility
  • I could have added the schedules to WCS_Action_Scheduler, but it seemed more appropriate to create a new class for notifications (WCS_Action_Scheduler_Customer_Notifications ), otherwise the scheduling logic might be a bit complex. It should be quite easy to change this, though.
  • The scheduling logic currently uses the state of the subscription object instead of deciding depending on which date/status is being updated. It was easier to grasp for me, given my inexperience with Subscriptions, but again, it should be possible to convert this to an approach similar to WCS_Action_Scheduler that relies entirely on Subscription status and which date is being updated (combining the logic of get_scheduled_action_hook, update_status and update_date methods).
  • Which conditions we want to check at schedule time vs at the time of sending the notification?
  • Do we want to unschedule notifications when store manager disables them?
    • Most likely not, as it's a lot of work to schedule/unschedule all of them. If they're disabled, this will create clutter in the db, though. Yes, we decided we will do this.

Side Notes For Testing

  • I had to downgrade to node 16 to build it
  • I had problems committing as phpcs was giving me errors. I updated ControlStructureSpacingSniff.php (converted last argument to array for ExtraSpaceBeforeCloseParenthesis and ExtraSpaceAfterCloseParenthesis sniffs), otherwise phpcs was failing. Perhaps there's a better way to solve this, but I didn't want to spend too much time investigating what's going on and what array should be passed where it gets a string. Updating squizlabs/php_codesniffer to 3.10.2 didn't help either.

TODO

How to test this PR

  1. Create a couple of different types of subscriptions: simple, variable, with free trial, with defined end-date/length, with free trial and length
  2. Buy these subscriptions, update them, renew them manually and automatically, cancel them.
  3. See if the notifications are being (un)scheduled as expected.
  • Added changelog entry (or does not apply)
  • Will this PR affect WooCommerce Subscriptions? yes/no/tbc, add issue ref
  • Will this PR affect WooCommerce Payments? yes/no/tbc, add issue ref
  • Added deprecated functions, hooks or classes to the spreadsheet

@peterfabian peterfabian self-assigned this Aug 5, 2024
Copy link
Member

@xristos3490 xristos3490 left a comment

Choose a reason for hiding this comment

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

Amazing work, @peterfabian! Loved how you incorporated the WCS_Scheduler structure and the date types! Everything seem to be falling into place 🙌

Left some initial thoughts on the structure before I start doing some hands-on testing.

@james-allan james-allan self-requested a review August 19, 2024 00:16
Copy link
Contributor

@james-allan james-allan left a comment

Choose a reason for hiding this comment

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

This looks pretty good @peterfabian. I haven't had a chance to test it today, however I've given it a skim in terms of reviewing the general approach taken.

I left some initial comments. :)

Just a heads up. I noticed in the PR array(). We have switched to use the shorthand syntax ([]) however haven't updated all existing uses. Our current approach is to use [] in newer code.

To allow easier batch processing, notifications are updated whenever subscription is updated after the settings changed, if the previous update of the subscription was before the settings changed.
@peterfabian
Copy link
Author

⚠️ Noting that these notification actions are still shown when the feature is disabled and trying to trigger them will silently do nothing because the feature is disabled. We can probably not show these actions when the feature is disabled.

Makes sense, fixed in 78e18e5.

@peterfabian
Copy link
Author

New Settings

This is very very minor and I would be happy to move this to another issue but I noticed the sizing of the settings are off:

This is a component reused from WC core, so perhaps I can address it over there.

@peterfabian
Copy link
Author

File naming

I found it a little difficult to find the files within /includes that related to this email notification feature, so I was thinking we could help that a little bit by either

  1. Sticking to a common file name structure i.e.:
  • class-wc-subscriptions-email-notifications.php
  • class-wc-subscriptions-email-notifications-scheduler.php
  • class-wc-subscriptions-email-notifications-batch-processor.php
  1. Move related files into a includes/email-notifications directory

I'm happy to go either way, what would be your preference?

I tried to blend the files in as I haven't seen other features being separated in the codebase. Since WC_Subscriptions_Email_Notifications was inspired by WC_Subscriptions_Email, I used similar naming:

  • class-wc-subscriptions-email.php
  • class-wc-subscriptions-email-notifications.php

Similarly, WCS_Action_Scheduler_Customer_Notifications was inspired by WCS_Action_Scheduler, so I used similar naming convention:

  • class-wcs-action-scheduler.php
  • class-wcs-action-scheduler-customer-notifications.php

etc

xristos3490 and others added 7 commits October 1, 2024 14:58
…unit tests (#689)

* Fix debug tool tests

* Test batch processor

* Test the emails controller

* Fix update time issue on first load

* Some polishing

* Fix emails controller tests

* Fix tests and initial state of DB

* More Cleanup;

* Bring hosted version of the batch controller and interface

* Fix tests to use the new interfaces

* Fix the autoloader

* Update the processors to use the new interfaces

* Fix the main file and enable feature for new installations fix

* Cleanup

* Cleanup

* Revert update-time rename

* Use the singleton and update logic to unschedule entities

* Improve debug tool copy

* Improve tests and docblocks

* More relative dates to the batch processor tests

* Revert v8 to x.x.x

* More test comments
* Updated the emails copy.

* Plain emails updated.

* Additional fixes.

* Renewals aren't always enabled.
Probably a merge mistake.
Copy link
Member

@xristos3490 xristos3490 left a comment

Choose a reason for hiding this comment

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

Looking good, @peterfabian! And the issue with the async date changes has been resolved! Left some nit comments mostly.

Copy link
Contributor

@james-allan james-allan left a comment

Choose a reason for hiding this comment

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

Hi @peterfabian. Sorry for how long it has taken to review this one. 😞

I think next week @mattallan and I need to move quickly to get this approved and merged so you folks can be released back to your normal work load. From there we can work from it as a base.

Our next scheduled release isn't until Nov 19th and so that should be plenty of time to have it running locally and apply fixes to if they come up.

I just skimmed the changes again today, there's a few function block comments etc missing, however that's all minor stuff. I'm going to carve out some time on Monday to really put it through the wringer and hopefully get it approved early.

$this->process_next_batch_for_single_processor( $batch_process );
},
10,
2
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is only accepting 1 arg isn't it? $batch_process

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I believe Peter is using a closure here to prevent others from removing that reference by calling "remove_action" for any reason.

cc @peterfabian

@peterfabian
Copy link
Author

Thanks @james-allan 🙌 All your input will be highly appreciated!

public function handle_woocommerce_debug_tools( array $tools ): array {

if ( ! WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) {
$tools['start_add_subscription_notifications'] = array(
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Just so I understand, this specific tool is added as a "stub" of sorts to the WooCommerce → Status → Tools even though the feature is disabled just for reference? It will always be disabled when the feature isn't enabled.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that's the idea. Instead of completely hiding this, we keep the UI consistent and use friendly messages.

Initially, before the mass unscheduling, this was (kinda) necessary in case the progressing unscheduling failed. So, essentially, the same handling was maintained after the improvement.

Perhaps the copy could use some improvement? I added some info in this PR if you'd like to take a look.

Screenshot 2024-10-21 at 5 09 21 PM

Copy link
Contributor

@james-allan james-allan left a comment

Choose a reason for hiding this comment

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

This looks good to merge imo. As I mentioned in my previous comment, this PR is super large and so I've mostly focused on reviewing critical architecture stuff. All the little things I noticed while testing and reviewing can be done in smaller, follow up PRs. I'll fill an issue to note those.

I've tested the following scenarios:

  • Enabling the setting schedules jobs for all valid subscriptions.
  • Changing the offset updates (cancels and reschedules) all jobs in the background.
  • Setting a long pre-event time like 2 months won't schedule any events for subscriptions with an event shorter than 2 months.
  • Setting a pre-event time like 7 days and then purchasing a weekly subscription won't schedule a pre-renewal email
    • 🔖 Note: I think this is fine given the alternative would be to send an email at the time of purchase and that would be spamming the customer. In this case, the "Thank you for your order" sent for the order includes the next payment date and so it serves the purpose of notifying the customer of the next payment date.
  • cancelling an subscriptions schedules the pre-expiration email.
  • Fully cancelling a subscription cancels the all pre-notification events.
  • Manual vs automatic subscriptions send different emails.
  • Renewing early pushes out the scheduled notification.
  • Updating the subscription status updates the scheduled notifications.
  • Updating the date programmatically updates the scheduled notifications.
  • Deleting a subscription date updates the scheduled notifications.
    • 🔖 Note: If you delete a trial end date, it doesn't schedule the pre-renewal email. I'll follow up with this later.
  • Emails aren't sent while in staging mode.
  • Running the actions from the Edit Subscription > Actions dropdown works as expected.
    • Noting that the actions listed match the subscription setup. Pre-trial only if in the trial period. Pre-renewal only when there's a next payment date and pre-end if there's a scheduled end date.
  • Synced subscriptions work as expected. The pre-renewal email is scheduled x days prior to the sync date.
  • Switching 🙈
    • Switching to a subscription on a different period updates the events. 👍

@peterfabian peterfabian marked this pull request as ready for review October 21, 2024 08:44
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.

5 participants