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

Cache subscription last order date #819

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

Conversation

alefesouza
Copy link
Member

@alefesouza alefesouza commented Mar 21, 2025

Fixes #782

Description

This PR improves the subscription list performance by caching the subscription last order date when new renewals are created or when the last order is deleted.

How to test this PR

Subscriptions list (existing subscription handling)

  1. Open phpMyAdmin.
  2. Run the following SQL Query:
SELECT * FROM `wp_wc_orders_meta` WHERE `meta_key` LIKE '%_last_order_date_created%'
  1. There should be no results.
  2. Open wp-admin and access WooCommerce > Subscriptions.
  3. Refresh the query on wp-admin, there should be a _last_order_date_created item for every loaded subscription on the subscriptions page.
  4. Add a breakpoint here and start the debugger.
  5. Reload the subscription page.
  6. If the debugger stops at the breakpoint, it means the data is being loaded from the cached metadata (_last_order_date_created) and it's not loading the all subscription orders as before.
  7. The Last Order Date column should be correctly rendered.

Subscription renewal

  1. Open any subscription.
  2. Look the _last_order_date_created value for the chosen subscription on phpMyAdmin.
  3. On Order actions, run Process renewal.
  4. The _last_order_date_created value for the chosen subscription should be updated.
  5. Open the subscriptions page.
  6. The Last Order Date column for the chosen subscription should show the correct date.

Subscription order deletion

  1. Open any subscription with renewal orders.
  2. Look the _last_order_date_created value for the chosen subscription on phpMyAdmin.
  3. Move to trash the subscription latest order.
  4. Permanently delete the order on the trashed order list.
  5. The _last_order_date_created value for the chosen subscription should be updated to the new latest order.
  6. The Last Order Date column for the chosen subscription should show the correct date.

Manual parent ID changes

  1. Open any subscription.
  2. On this line, add the following code:
add_action(
	'woocommerce_admin_order_data_after_order_details',
		function () {
			$subscription = wcs_get_subscription( 259 ); // Choose any subscription
			$subscription->set_parent_id( 256 ); // Choose any subscription order from another subscription
			$subscription->save();
		},
	10
);
  1. Open the chosen subscription.
  2. Open the Subscriptions page.
  3. If the new subscription parent order id is higher than the other others, its date should show up on the Last Order Date column.
  4. The Last Order Date column for the old parent id should also be updated.

Add order relation

  1. Open any subscription.
  2. On this line, add the following code:
add_action(
	'woocommerce_admin_order_data_after_order_details',
		function () {
			$order = wc_get_order( 282 ); // Choose any child order
			$subscription = wcs_get_subscription( 259 ); // Choose any subscription

			WCS_Related_Order_Store::instance()->add_relation( $order, $subscription, 'renewal' );
		},
	10
);
  1. Open the chosen subscription.
  2. Open the Subscriptions page.
  3. If the new subscription related order id is higher than the other others, its date should show up on the Last Order Date column.

Delete order relation

  1. Open any subscription.
  2. On this line, add the following code:
add_action(
	'woocommerce_admin_order_data_after_order_details',
		function () {
			$subscription = wcs_get_subscription( 259 ); // Choose any subscription
			$order = wc_get_order( 282 ); // Choose the latest child order

			WCS_Related_Order_Store::instance()->delete_relation( $order, $subscription, 'renewal' );
		},
	10
);
  1. Open the chosen subscription.
  2. Open the Subscriptions page.
  3. The Last Order Date column should show the date of the new latest order.

Product impact

  • 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

@alefesouza alefesouza requested a review from mattallan March 21, 2025 01:45
Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

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

Thanks @alefesouza for picking up this issue!

I like your approach with only using this new cached date value for display purposes and not in calculations as that reduces the risk of any serious breakages 😅 I think this is a good starting position.

Taking a look at the last_order_date_created date, it's currently calculated each time by fetching the list of parent, renewal, switch orders linked to the subscription, and then gets maximum order ID from this list. Reads the order into memory and then returns the data_created value.

In the crazy world of subscriptions, some subscriptions don't have parent orders, some renewal orders can be created manually & the link between subscription and related orders can be freely added and removed using code like:

WCS_Related_Order_Store::instance()->add_relation( $order, $subscription, 'renewal' ); // Manually connects a renewal order to a subscription.
...
WCS_Related_Order_Store::instance()->delete_relation( $order, $subscription, 'switch' ); // Unlinks a switch order from a subscription.

With that all said, to ensure we're displaying the most up-to-date Last Order Data value on the WooCommerce > Subscriptions table, we'll need to make sure we're updating/recalculating the cache for each of the following cases:

Parent order changes

  • When someone manually assigns a new parent order to a subscription i.e
$subscription->set_parent_id( 1234 );
$subscription->save();
  • When creating a pending order via the Subscription Actions
    image
  • When purchasing a new subscription through the checkout

Renewal order changes

  • When a new renewal order is manually created via the Subscription Actions
  • When a new renewal is automatically created via action scheduler
  • When a payment gateway manually creates a renewal order for gateway scheduled renewals (i.e stripe billing, paypal subscriptions)
  • When an existing renewal order is manually linked to a subscription via WCS_Related_Order_Store::instance()->add_relation()
  • When a renewal order is deleted via any means
  • When a renewal order is unlinked WCS_Related_Order_Store::instance()->delete_relation()

Switch order changes

  • When a customer processes a upgrade/downgrade via the My Account
  • When an order is manually linked as a switch on a subscription WCS_Related_Order_Store::instance()->add_relation()
  • When a switch order is deleted or unlinked via delete_relation

Having to support this list of cases may seem a bit intimidating but I think one way we could achieve this is to have a new class that makes use of our WCS_Object_Data_Cache_Manager (for HPOS) and WCS_Post_Meta_Cache_Manager (WP Posts).

I haven't explored this yet myself, but I think we should be able to instantiate a new WCS_Object_Data_Cache_Manager|WCS_Post_Meta_Cache_Manager class and pass it 'subscription' order type and then a list of meta keys that we want to "watch". Something like:

$cache_manager = new WCS_Object_Data_Cache_Manager( 'subscription', [
 'parent_id', // don't know if this works.
 '_subscription_renewal_order_ids_cache', // don't know if this will work for internal meta keys.
 '_subscription_switch_order_ids_cache',
] );
$cache_manager->init();

The thinking behind this idea is that our related orders cache handler is already watching for all of these changes to renewal and switch orders and keeping the related order cache up-to-date. So in theory, we should be able to watch for changes to these related orders cache and then trigger recalculating the Last Order Date.

The only meta changes we're currently not watching which also affects the last order date, is the parent ID. I'm curious if we can watch for updates to the parent_id meta on the subscription to know when a new parent order is set (?) 🤔

There's a high chance I'm over complicating this, or it simply doesn't work, and so very simple approach might be to just to hook onto the woocommerce_update_subscription hook and delete the date cache. This will then trigger us to recalculate the date the next time it's fetched.

Keen to hear your thoughts on this! :)


🥚 Hope you guys are all good and good luck with everything! <3

@leonardola leonardola self-requested a review April 4, 2025 19:17
@alefesouza
Copy link
Member Author

@leonardola this PR is ready to review


add_action( 'wcs_orders_add_relation', [ __CLASS__, 'add_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );

add_action( 'wcs_orders_delete_relation', [ __CLASS__, 'delete_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );
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 name seems weird to me. Also it does not actually delete the order just update it

Copy link
Member Author

Choose a reason for hiding this comment

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

@leonardola it deletes the order relation, not the order itself.

@alefesouza alefesouza requested a review from leonardola April 8, 2025 00:20
Copy link
Contributor

@leonardola leonardola left a comment

Choose a reason for hiding this comment

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

Under Manual parent ID changes

If the new subscription parent order id is higher than the other others, its date should show up on the Last Order Date column.

Shouldn't it update the latest order every time independently if the new parent is newer or older?

To me even when I update the parent order to a newer one it doesn't update the last order date

Under Add order relation

In this case the newest order was used to calculate the last order date even though I added an older order. In this case I think the current behavior is correct.


add_action( 'wcs_orders_add_relation', [ __CLASS__, 'add_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );

add_action( 'wcs_orders_delete_relation', [ __CLASS__, 'delete_relation_update_order_related_subscriptions_last_order_date_created' ], 10, 3 );
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 name seems weird to me. Also it does not actually delete the order just updates it

Copy link
Member Author

Choose a reason for hiding this comment

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

@leonardola the delete_ is the function names comes from WCS_Related_Order_Store::instance()->delete_relation(, it's not related to delete an order but delete a relation.

* @param WC_Order $subscription A subscription or order to unlink the order with, if a relation exists.
* @param string $relation_type The relationship between the subscription and the order. Must be 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented.
*/
public static function delete_relation_update_order_related_subscriptions_last_order_date_created( $order, $subscription, $relation_type ) {
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 also not deleting anything is it?

Copy link
Member Author

Choose a reason for hiding this comment

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

The function name comes from WCS_Related_Order_Store::instance()->delete_relation(, would you suggest a better name?

@@ -277,6 +277,11 @@ function wcs_create_order_from_subscription( $subscription, $type ) {
throw new Exception( sprintf( __( 'There was an error fetching the new order (%1$s) for subscription %2$d.', 'woocommerce-subscriptions' ), $type, $subscription->get_id() ) );
}

// Update the subscription last_order_date_created on every time
Copy link
Contributor

Choose a reason for hiding this comment

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

make the comment a one liner or change to /* */ style

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed on ef28be8.

@alefesouza alefesouza requested a review from leonardola April 12, 2025 00:48
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.

Improve performance rendering the Last Order Date column value on the WooCommerce > Subscriptions list table
3 participants