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

Add simple products to the Analytics orders query for the attributes filter #44901

Merged
merged 7 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fix

Include simple product support in the attributes filter within the analytics orders view.
24 changes: 22 additions & 2 deletions plugins/woocommerce/src/Admin/API/Reports/DataStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,7 @@ protected function get_attribute_subqueries( $query_args ) {
continue;
}

$term_id = '';
// If the tuple is numeric, assume these are IDs.
if ( is_numeric( $attribute_term[0] ) && is_numeric( $attribute_term[1] ) ) {
$attribute_id = intval( $attribute_term[0] );
Expand Down Expand Up @@ -1374,6 +1375,10 @@ protected function get_attribute_subqueries( $query_args ) {
// Assume these are a custom attribute slug/value pair.
$meta_key = esc_sql( $attribute_term[0] );
$meta_value = esc_sql( $attribute_term[1] );
$attr_term = get_term_by( 'slug', $meta_value, $meta_key );
if ( false !== $attr_term ) {
$term_id = $attr_term->term_id;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

$term_id wasn't previously being used, but now it is, this makes sure it is set in both instances.

}

$join_alias = 'orderitemmeta1';
Expand All @@ -1391,8 +1396,23 @@ protected function get_attribute_subqueries( $query_args ) {
$sql_clauses['join'][] = "JOIN {$wpdb->prefix}woocommerce_order_itemmeta as {$join_alias} ON {$join_alias}.order_item_id = {$table_to_join_on}.order_item_id";
}

// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$sql_clauses['where'][] = $wpdb->prepare( "( {$join_alias}.meta_key = %s AND {$join_alias}.meta_value {$comparator} %s )", $meta_key, $meta_value );
$in_comparator = '=' === $comparator ? 'in' : 'not in';

// Add subquery for products ordered using attributes not used in variations.
$term_attribute_subquery = "select product_id from {$wpdb->prefix}wc_product_attributes_lookup where is_variation_attribute=0 and term_id = %s";
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
$sql_clauses['where'][] = $wpdb->prepare(
"
( ( {$join_alias}.meta_key = %s AND {$join_alias}.meta_value {$comparator} %s ) or (
{$wpdb->prefix}wc_order_product_lookup.variation_id = 0 and {$wpdb->prefix}wc_order_product_lookup.product_id {$in_comparator} ({$term_attribute_subquery})
) )",
$meta_key,
$meta_value,
$term_id,
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:enable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ public function test_product_attributes_filter() {
$order_variation_1 = wc_get_product( $product_variations[1] ); // Variation: size = large.
$order_variation_2 = wc_get_product( $product_variations[3] ); // Variation: size = huge, colour = red, number = 2.

// Create a simple product.
$size_attr_id = wc_attribute_taxonomy_id_by_name( 'pa_size' );
$large_term = get_term_by( 'slug', 'large', 'pa_size' );

$global_attribute = new WC_Product_Attribute();
$global_attribute->set_id( $size_attr_id );
$global_attribute->set_name( 'pa_size' );
$global_attribute->set_options( array( $large_term->term_id ) ); // Set to small.
$global_attribute->set_position( 1 );
$global_attribute->set_visible( true );
$global_attribute->set_variation( false );
$attributes['global-size'] = $global_attribute;

$simple_product = WC_Helper_Product::create_simple_product(
true,
array(
'attributes' => $attributes,
)
);

// Create orders for variations.
$variation_order_1 = WC_Helper_Order::create_order( $this->user, $order_variation_1 );
$variation_order_1->set_status( 'completed' );
Expand All @@ -156,6 +176,10 @@ public function test_product_attributes_filter() {
$variation_order_2->set_status( 'completed' );
$variation_order_2->save();

$simple_product_order_1 = WC_Helper_Order::create_order( $this->user, $simple_product );
$simple_product_order_1->set_status( 'completed' );
$simple_product_order_1->save();

// Create more orders for simple products.
for ( $i = 0; $i < 10; $i++ ) {
$order = WC_Helper_Order::create_order( $this->user );
Expand All @@ -172,25 +196,23 @@ public function test_product_attributes_filter() {

// Sanity check before filtering by attribute.
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 12, $response_orders['totals']['orders_count'] );
$this->assertEquals( 13, $response_orders['totals']['orders_count'] );

// Filter by the "size" attribute, with value "large".
$size_attr_id = wc_attribute_taxonomy_id_by_name( 'pa_size' );
$small_term = get_term_by( 'slug', 'large', 'pa_size' );
$request = new WP_REST_Request( 'GET', $this->endpoint );
$request = new WP_REST_Request( 'GET', $this->endpoint );
$request->set_query_params(
array(
'attribute_is' => array(
array( $size_attr_id, $small_term->term_id ),
array( $size_attr_id, $large_term->term_id ),
),
)
);
$response = $this->server->dispatch( $request );
$response_orders = $response->get_data();

$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, $response_orders['totals']['orders_count'] );
$this->assertEquals( $variation_order_1->get_total(), $response_orders['totals']['total_sales'] );
$this->assertEquals( 2, $response_orders['totals']['orders_count'] );
$this->assertEquals( $variation_order_1->get_total() + $simple_product_order_1->get_total(), $response_orders['totals']['total_sales'] );

// Filter by excluding the "size" attribute, with value "large".
$size_attr_id = wc_attribute_taxonomy_id_by_name( 'pa_size' );
Expand All @@ -207,8 +229,8 @@ public function test_product_attributes_filter() {
$response_orders = $response->get_data();

$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, $response_orders['totals']['orders_count'] );
$this->assertEquals( 11, $response_orders['totals']['orders_count'] );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is 11 now, because previously even when using is_not it would filter out any orders with simple products. Now the query actually works correctly for is_not.

// This should be the second variation order.
$this->assertEquals( $variation_order_2->get_total(), $response_orders['totals']['total_sales'] );
$this->assertEquals( 11 * $variation_order_2->get_total(), $response_orders['totals']['total_sales'] );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,26 @@ public function test_product_attributes_filter() {
$order_variation_1 = wc_get_product( $product_variations[0] ); // Variation: size = small.
$order_variation_2 = wc_get_product( $product_variations[2] ); // Variation: size = huge, colour = red, number = 0.

// Create a simple product.
$size_attr_id = wc_attribute_taxonomy_id_by_name( 'pa_size' );
$small_term = get_term_by( 'slug', 'small', 'pa_size' );

$global_attribute = new WC_Product_Attribute();
$global_attribute->set_id( $size_attr_id );
$global_attribute->set_name( 'pa_size' );
$global_attribute->set_options( array( $small_term->term_id ) ); // Set to small.
$global_attribute->set_position( 1 );
$global_attribute->set_visible( true );
$global_attribute->set_variation( false );
$attributes['global-size'] = $global_attribute;

$simple_product = WC_Helper_Product::create_simple_product(
true,
array(
'attributes' => $attributes,
)
);

// Create orders for variations.
$variation_order_1 = WC_Helper_Order::create_order( $this->user, $order_variation_1 );
$variation_order_1->set_status( 'completed' );
Expand All @@ -158,6 +178,10 @@ public function test_product_attributes_filter() {
$variation_order_2->set_status( 'completed' );
$variation_order_2->save();

$simple_product_order_1 = WC_Helper_Order::create_order( $this->user, $simple_product );
$simple_product_order_1->set_status( 'completed' );
$simple_product_order_1->save();

// Create more orders for simple products.
for ( $i = 0; $i < 10; $i++ ) {
$order = WC_Helper_Order::create_order( $this->user );
Expand All @@ -174,11 +198,10 @@ public function test_product_attributes_filter() {

// Sanity check before filtering by attribute.
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 12, count( $response_orders ) );
$this->assertEquals( 13, count( $response_orders ) );

// To filter by later.
$size_attr_id = wc_attribute_taxonomy_id_by_name( 'pa_size' );
$small_term = get_term_by( 'slug', 'small', 'pa_size' );

// Test bad values to filter parameter.
$bad_args = array(
Expand All @@ -201,7 +224,7 @@ public function test_product_attributes_filter() {

$this->assertEquals( 200, $response->get_status() );
// We expect all results since the attribute param is malformed.
$this->assertEquals( 12, count( $response_orders ) );
$this->assertEquals( 13, count( $response_orders ) );
}

// Filter by the "size" attribute, with value "small".
Expand All @@ -217,8 +240,15 @@ public function test_product_attributes_filter() {
$response_orders = $response->get_data();

$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, count( $response_orders ) );
$this->assertEquals( $response_orders[0]['order_id'], $variation_order_1->get_id() );
$this->assertEquals( 2, count( $response_orders ) );
$order_ids = array_map(
function( $order ) {
return $order['order_id'];
},
$response_orders
);
$this->assertContains( $simple_product_order_1->get_id(), $order_ids );
$this->assertContains( $variation_order_1->get_id(), $order_ids );

// Verify the opposite result set.
$request = new WP_REST_Request( 'GET', $this->endpoint );
Expand All @@ -234,8 +264,7 @@ public function test_product_attributes_filter() {
$response_orders = $response->get_data();

$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, count( $response_orders ) );
$this->assertEquals( $response_orders[0]['order_id'], $variation_order_2->get_id() );
$this->assertEquals( 11, count( $response_orders ) );
}

/**
Expand Down Expand Up @@ -427,7 +456,7 @@ public function test_order_price_formatting_with_different_base_currency() {

// Create another simple order with another currency.
$currencies = get_woocommerce_currencies();
// prevent base currency to be selected again
// prevent base currency to be selected again.
unset( $currencies[ get_woocommerce_currency() ] );
$second_currency = array_rand( $currencies );

Expand All @@ -439,7 +468,7 @@ public function test_order_price_formatting_with_different_base_currency() {

WC_Helper_Queue::run_all_pending();

// Get the created orders from REST API
// Get the created orders from REST API.
$request = new WP_REST_Request( 'GET', $this->endpoint );
$request->set_query_params(
array(
Expand Down